无向图的算法:Kruskal算法与Prim算法生成最小生成树

简介: 无向图的算法:Kruskal算法与Prim算法生成最小生成树

最小生成树

先来看一个问题:

网络异常,图片无法展示
|

在上述图中,描述了学校、农场等6个地点,并用权值标志了各个地点之间的道路距离,现在假设我需要用最小的边,去连通图中所有的地点,这个最小边连通的树就是它的最小生成树。

生成树的属性

  • 一个连通图可以有多个生成树;
  • 一个连通图的所有生成树都包含相同的顶点个数和边数;
  • 生成树当中不存在环;
  • 移除生成树中的任意一条边都会导致图的不连通, 生成树的边最少特性;
  • 在生成树中添加一条边会构成环。
  • 对于包含n个顶点的连通图,生成树包含n个顶点和n-1条边;
  • 对于包含n个顶点的无向完全图最多包含nn−2n^{n-2}nn2颗生成树。

最小生成树

最小生成树,就是无向图中边的权值最小的生成树

对于一个带权连通无向图G=(V,E),生成树不同,每棵树的权(树中所有边上的权值和)也不同,设R为G的所有生成树的集合,若T为R中权值和最小的生成树,则T称为G的最小生成树(Minimum-Spanning-Tree,MST)

注:

  • 1、最小生成树可能有多个,但边的权值之和总是唯一且最小的
  • 2、最小生成树的边数=定点数-1,砍掉一条则不连通,增加一条则会出现回路
  • 3、若一个连通图本身就是一颗树,则其最小生成树就是它本身
  • 4、只有连通图才有生成树,非连通图只有生成森林

生成最小生成树的算法有两种:Kruskal算法(克鲁斯卡尔)、普里姆算法(Prim)。

Kruskal算法

每次选择 权值最小且不违反最小生成树属性(不构成环) 的边,使这条边的两头连通(原本已近连通则不选)直到所有结点都连通。

思路

  • 1.把所有边进行排序
  • 2.先把最小的边考虑进去,然后再看是否形成环,如果成环了就不要把这个边考虑进去,如果没有,进入下一步
  • 3.再把下一个小的边考虑进去,再判断是否形成环,如果成环了就不要把这个边考虑进去,如果没有,进入下一步
  • 4.直到连通所有

难点

Kruskal算法的难点在于:如何判断添加一条边后是否形成环?

主要是在于实现集合查询与集合合并。首先,每个点都属于一个单独的集合,当把一个边考虑进去的时候,先判断大的集合里有没有包含这个节点,如果里面有这个节点了就表示形成环了,否则就将把这个节点所在的集合合并进去。

代码实现

public class Kruskal {
     // 这样是可以的,但是没有并查集快
    public static class MySets{
        public HashMap<Node, List<Node>> setMap;
        public MySets(List<Node> nodes){
            for(Node cur: nodes){
                List<Node> set = new ArrayList<Node>();
                set.add(cur);
                setMap.put(cur, set);
            }
        }
        public MySets(List<Node> nodes){
            for(Node cur: nodes){
                List<Node> set = new ArrayList<Node>();
                set.add(cur);
                setMap.put(cur, set);
            }
        }
        public MySets(List<Node> nodes){
            for(Node cur: nodes){
                List<Node> set = new ArrayList<Node>();
                set.add(cur);
                setMap.put(cur, set);
            }
        }
    }
    // K算法(并查集)
    public static Set<Edge> KruskalMST(Gragh graph){
        UnionFind unionFind = new UnionFind();  // 生成一个并查集结构
        unionFind.makeSets(graph.nodes.values());
        // 利用比较器将边进行排序
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
        for(Edge edge:graph.edges){  // M条边
            priorityQueue.add(edge);   // O(logM)
        }
        Set<Edge> result = new HashSet<>();
        while(!priorityQueue.isEmpty()){   // M条边
            Edge edge = priorityQueue.poll();    //  O(logM)
            if(!unionFind.isSameSet(edge.form, edge.to)){   // O(1)
                result.add(edge);
                unionFind.union(edge.from, edge.to);
            }
        }
        return result;
    }
}
复制代码

Prim算法

举个例子

网络异常,图片无法展示
|

如图所示,如果我们要对它进行Prim算法操作,那么数据演示如下:

  1. 假设从A点出发(Prim算法对出发的点没有要求,随意选一个出发点即可),此时就释放了三条边:A-B、A-C、A-D,从中选取权值最小的边A-C,那么A、C两点被选中
  2. C点被选中,那么C-B、C-D、C-E、C-F四个边也被释放,从中选取权值最小的边C-F(A点已经被选中了,所以不能选C-A这个边了),此时A、C、F三点被选中
  3. F点被选中,那么F-D、F-E两个边也被释放,从中选取权值最小的边F-D,此时A、C、F、D四点被选中
  4. D点被选中,没有新的边需要被释放,此时在全局范围内(指被释放的但未曾被选中权值最小的边)挑权值最小的边,也就是A-B(6)、A-D(5)、C-B(5)、C-D(5)、C-E(6)、F-E(6)中挑选,其中A-D(5)、C-D(5)涉及的点都曾被选中了,因此只能选C-B(5)这个边,此时A、C、F、D、B五点被选中
  5. B点被选中,B-E边被释放,此时A、C、F、D、B、E已经全部被选中,最小生成树已经形成,A-C、C-F、F-D、C-B、B-E所形成的就是最小生成树

代码实现

public static class EdgeComparator implements Comparator<Edge>{
    public int compare(Edge o1, Edge o2){
        return o1.weight - o2.weight
    }
}
public static Set<Edge> primMST(Graph graph){
    // 解锁的边进入小根堆
    PriorityQueue<Edge> PriorityQueue = new PriorityQueue<>(new EdgeComparator());
    HashSet<Node> set = new HashSet<>();
    Set<Edge> result = new HashSet<>();  // 依次挑选的边在result里
    for(Node node: graph.nodes.values()){  // 随便挑了一个点
        // node 是开始点
        if(!set.contains(node)){
            set.add(node);
            for(Edge edge: node.edges){   // 由一个点,解锁所有相连的边
                priorityQueue.add(edge);
            }
            while(!priorityQueue.isEmpty()){
                Edge edge = priorityQueue.poll(); // 弹出解锁的边中,最小的边
                Node toNode = edge.to;     // 可能的一个新的点
                if(!set.contains(toNode)){     // 不含有的时候,就是新的点
                    set.add(toNode);
                    result.add(edge);
                    for(Edge nextEdge: toNode.edges){
                        priorityQueue.add(nextEdge);
                    }
                }
            }
        }
    }
    return result;
}
复制代码

相关Leetcode题

剑指 Offer II 118. 多余的边

684. 冗余连接

1584. 连接所有点的最小费用

685. 冗余连接 II

721. 账户合并

1135. 最低成本联通所有城市

1489. 找到最小生成树里的关键边和伪关键边

305. 岛屿数量 II

相关链接

图解:什么是最小生成树?

最小生成树


相关文章
|
4月前
|
算法 索引
class061 最小生成树【算法】
class061 最小生成树【算法】
50 0
|
6月前
|
算法
最小生成树算法:Prim算法
在图论中,最小生成树(Minimum Spanning Tree,简称MST)是一种常用的算法问题。最小生成树是指在一个加权连通图中选取边的子集,使得所有顶点都被覆盖,并且边的总权值最小。
160 0
|
2天前
|
算法 索引
数据结构与算法-最小生成树入门
数据结构与算法-最小生成树入门
8 0
|
13天前
|
存储 机器学习/深度学习 算法
上机实验三 图的最小生成树算法设计 西安石油大学数据结构
上机实验三 图的最小生成树算法设计 西安石油大学数据结构
19 1
|
2月前
|
算法 Java C++
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-6 算法训练 安慰奶牛 最小生成树
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-6 算法训练 安慰奶牛 最小生成树
23 0
|
4月前
|
算法 搜索推荐
Kruskal算法
Kruskal算法
|
5月前
|
算法 C++
用prim和kruskal算法求最小生成树问题
用prim和kruskal算法求最小生成树问题
47 0
|
8月前
|
存储 算法 C++
最小生成树问题及Kruskal算法的解析
最小生成树问题及Kruskal算法的解析
73 2
|
9月前
|
供应链 算法 Java
java数据结构最经济的地下通道建设方案prim算法
MX是世界上第一大传媒娱乐企业,该公司数十年的经营历史中创作了很多经典影片,此外还经营着很多的规模十分宏大世界级的主题娱乐公园。最近MX公司刚和C国X城市达成协定,共同投资建设C国国内唯一一家主题娱乐公园。
48 0
|
1月前
|
传感器 算法 计算机视觉
基于肤色模型和中值滤波的手部检测算法FPGA实现,包括tb测试文件和MATLAB辅助验证
该内容是关于一个基于肤色模型和中值滤波的手部检测算法的描述,包括算法的运行效果图和所使用的软件版本(matlab2022a, vivado2019.2)。算法分为肤色分割和中值滤波两步,其中肤色模型在YCbCr色彩空间定义,中值滤波用于去除噪声。提供了一段核心程序代码,用于处理图像数据并在FPGA上实现。最终,检测结果输出到&quot;hand.txt&quot;文件。