无向图的算法: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

相关链接

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

最小生成树


相关文章
|
2月前
|
算法 决策智能
基于prim算法求出网络最小生成树实现网络社团划分和规划
该程序使用MATLAB 2022a版实现路线规划,通过排序节点权值并运用Prim算法生成最小生成树完成网络规划。程序基于TSP问题,采用遗传算法与粒子群优化算法进行路径优化。遗传算法通过编码、选择、交叉及变异操作迭代寻优;粒子群优化算法则通过模拟鸟群觅食行为,更新粒子速度和位置以寻找最优解。
|
4月前
|
机器学习/深度学习 算法 Java
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
这篇文章介绍了基于贪婪技术思想的Prim算法和Dijkstra算法,包括它们的伪代码描述、Java源代码实现、时间效率分析,并展示了算法的测试用例结果,使读者对贪婪技术及其应用有了更深入的理解。
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
|
5月前
|
存储 传感器 算法
|
5月前
|
机器学习/深度学习 人工智能 算法
|
6月前
|
算法 Java
Java数据结构与算法:贪心算法之最小生成树
Java数据结构与算法:贪心算法之最小生成树
|
6月前
|
算法 C语言
数据结构与算法——最小生成树问题(什么是最小生成树、Prim算法、Kruskal算法)
数据结构与算法——最小生成树问题(什么是最小生成树、Prim算法、Kruskal算法)
40 0
|
7月前
|
存储 机器学习/深度学习 算法
上机实验三 图的最小生成树算法设计 西安石油大学数据结构
上机实验三 图的最小生成树算法设计 西安石油大学数据结构
96 1
|
7月前
|
人工智能 算法
一些算法的复习(最短路径、最小生成树、dp)
一些算法的复习(最短路径、最小生成树、dp)
|
7月前
|
算法
最小生成树算法
最小生成树算法
|
7月前
|
算法 Java C++
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-6 算法训练 安慰奶牛 最小生成树
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-6 算法训练 安慰奶牛 最小生成树
50 0