2

Kruskal 最小生成树算法

 1 year ago
source link: https://labuladong.github.io/algo/2/20/51/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kruskal 最小生成树算法

通知: 持续更新中, 开始报名, 开始预约。

读完本文,你不仅学会了算法套路,还可以顺便解决如下题目:

牛客 LeetCode 力扣 难度
-

🔒

🔒

🟠
- 🟠
-

🔒

🔒

🟠

———–

图论中知名度比较高的算法应该就是

以及今天要讲的最小生成树(Minimum Spanning Tree)算法了。

最小生成树算法主要有 Prim 算法(普里姆算法)和 Kruskal 算法(克鲁斯卡尔算法)两种,这两种算法虽然都运用了贪心思想,但从实现上来说差异还是蛮大的。

本文先来讲比较简单易懂的 Kruskal 算法,然后在下一篇文章

中聊 Prim 算法。

Kruskal 算法其实很容易理解和记忆,其关键是要熟悉并查集算法,如果不熟悉,建议先看下前文

接下来,我们从最小生成树的定义说起。

什么是最小生成树

先说「树」和「图」的根本区别:树不会包含环,图可以包含环

如果一幅图没有环,完全可以拉伸成一棵树的模样。说的专业一点,树就是「无环连通图」。

那么什么是图的「生成树」呢,其实按字面意思也好理解,就是在图中找一棵包含图中的所有节点的树。专业点说,生成树是含有图中所有顶点的「无环连通子图」。

容易想到,一幅图可以有很多不同的生成树,比如下面这幅图,红色的边就组成了两棵不同的生成树:

1.png

对于加权图,每条边都有权重,所以每棵生成树都有一个权重和。比如上图,右侧生成树的权重和显然比左侧生成树的权重和要小。

那么最小生成树很好理解了,所有可能的生成树中,权重和最小的那棵生成树就叫「最小生成树」

PS:一般来说,我们都是在无向加权图中计算最小生成树的,所以使用最小生成树算法的现实场景中,图的边权重一般代表成本、距离这样的标量。

在讲 Kruskal 算法之前,需要回顾一下 Union-Find 并查集算法。

Union-Find 并查集算法

刚才说了,图的生成树是含有其所有顶点的「无环连通子图」,最小生成树是权重和最小的生成树。

那么说到连通性,相信老读者应该可以想到 Union-Find 并查集算法,用来高效处理图中联通分量的问题。

前文

详细介绍了 Union-Find 算法的实现原理,主要运用 size 数组和路径压缩技巧提高连通分量的判断效率。

如果不了解 Union-Find 算法的读者可以去看前文,为了节约篇幅,本文直接给出 Union-Find 算法的实现:

class UF {
    // 连通分量个数
    private int count;
    // 存储一棵树
    private int[] parent;
    // 记录树的「重量」
    private int[] size;

    // n 为图中节点的个数
    public UF(int n) {
        this.count = n;
        parent = new int[n];
        size = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }
    
    // 将节点 p 和节点 q 连通
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ)
            return;
        
        // 小树接到大树下面,较平衡
        if (size[rootP] > size[rootQ]) {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
        } else {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
        }
        // 两个连通分量合并成一个连通分量
        count--;
    }

    // 判断节点 p 和节点 q 是否连通
    public boolean connected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        return rootP == rootQ;
    }

    // 返回节点 x 的连通分量根节点
    private int find(int x) {
        while (parent[x] != x) {
            // 进行路径压缩
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        return x;
    }

    // 返回图中的连通分量个数
    public int count() {
        return count;
    }
}

前文

介绍过 Union-Find 算法的一些算法场景,而它在 Kruskal 算法中的主要作用是保证最小生成树的合法性。

因为在构造最小生成树的过程中,你首先得保证生成的那玩意是棵树(不包含环)对吧,那么 Union-Find 算法就是帮你干这个事儿的。

怎么做到的呢?先来看看力扣第 261 题「

」,我描述下题目:

给你输入编号从 0n - 1n 个结点,和一个无向边列表 edges(每条边用节点二元组表示),请你判断输入的这些边组成的结构是否是一棵树。

函数签名如下:

boolean validTree(int n, int[][] edges);

比如输入如下:

n = 5
edges = [[0,1], [0,2], [0,3], [1,4]]

这些边构成的是一棵树,算法应该返回 true:

2.png

但如果输入:

n = 5
edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]

形成的就不是树结构了,因为包含环:

3.png

对于这道题,我们可以思考一下,什么情况下加入一条边会使得树变成图(出现环)

显然,像下面这样添加边会出现环:

4.png

而这样添加边则不会出现环:

5.png

总结一下规律就是:

对于添加的这条边,如果该边的两个节点本来就在同一连通分量里,那么添加这条边会产生环;反之,如果该边的两个节点不在同一连通分量里,则添加这条边不会产生环

而判断两个节点是否连通(是否在同一个连通分量中)就是 Union-Find 算法的拿手绝活,所以这道题的解法代码如下:

// 判断输入的若干条边是否能构造出一棵树结构
boolean validTree(int n, int[][] edges) {
    // 初始化 0...n-1 共 n 个节点
    UF uf = new UF(n);
    // 遍历所有边,将组成边的两个节点进行连接
    for (int[] edge : edges) {
        int u = edge[0];
        int v = edge[1];
        // 若两个节点已经在同一连通分量中,会产生环
        if (uf.connected(u, v)) {
4.png
return false; } // 这条边不会产生环,可以是树的一部分 uf.union(u, v);
5.png
} // 要保证最后只形成了一棵树,即只有一个连通分量 return uf.count() == 1; } class UF { // 见上文代码实现 }

如果你能够看懂这道题的解法思路,那么掌握 Kruskal 算法就很简单了。

Kruskal 算法

所谓最小生成树,就是图中若干边的集合(我们后文称这个集合为 mst,最小生成树的英文缩写),你要保证这些边:

1、包含图中的所有节点。

2、形成的结构是树结构(即不存在环)。

3、权重和最小。

有之前题目的铺垫,前两条其实可以很容易地利用 Union-Find 算法做到,关键在于第 3 点,如何保证得到的这棵生成树是权重和最小的。

_____________

应合作方要求,本文不便在此发布,请扫码关注回复关键词「生成树」或

查看:

qrcode.jpg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK