Menu
CoddyTech

Kruskal 算法

最后更新

Kruskal 算法用来构建最小生成树(MST),也就是连接所有节点、没有环、总权重最小的一组边。它先把所有边按权重排序,然后贪心地依次加入当前最便宜的边,前提是这条边连接的两个节点还没有连通。点击上方的“播放”,观看这棵树如何每次加入一条最便宜的安全边,逐渐长大。

“是否已经连通”的判断由并查集(不相交集合)数据结构完成:如果一条边的两个端点已经在同一个连通分量中,就跳过它,因为加入它会形成环。排序是主要开销,所以总复杂度是 O(E log E)。Kruskal 算法特别适合稀疏图。

时间复杂度与空间复杂度

指标复杂度说明
时间O(E log E)主要花在对边排序上
并查集操作≈ O(E α(V))使用路径压缩后,每次判断接近常数时间
空间O(V + E)边列表 + 不相交集合结构
最适合稀疏图基于全局边列表工作

分步说明

步骤发生了什么
1把所有边按权重升序排序。
2让每个节点各自成为一个连通分量(并查集)。
3取出下一条最便宜的边。
4如果它的两个端点在不同的连通分量中,就把它加入树中,并合并这两个分量。
5否则跳过它(加入会形成环)。
6当树有 V − 1 条边时停止。

实例演示

图中有节点 A, B, C, D 和边 A-B(1)、B-C(2)、A-C(3)、C-D(4)、B-D(5)。排序后的边:A-B(1)、B-C(2)、A-C(3)、C-D(4)、B-D(5):

边(权重)处理前的连通分量操作
A-B(1){A} {B} {C} {D}属于不同分量:加入 MST,合并为 {A,B}。
B-C(2){A,B} {C} {D}属于不同分量:加入 MST,合并为 {A,B,C}。
A-C(3){A,B,C} {D}A 和 C 已经连通:跳过(会形成环)。
C-D(4){A,B,C} {D}属于不同分量:加入 MST,合并为 {A,B,C,D}。
停止{A,B,C,D}树已有 V - 1 = 3 条边。MST = A-B, B-C, C-D,总权重 7。

什么时候用 Kruskal 算法

适合使用不适合使用
图是稀疏的(边数相对节点数较少)。图是稠密的:这时用堆实现的 Prim 算法通常更快。
边已经以全局列表的形式存在,可以直接排序。边只能通过邻接表获得,需要逐个节点扫描。
你想要一个基于并查集的简单实现。你需要让树从某个特定的起始节点开始生长。
你想为一个不连通的图构建生成森林。边以流的形式不断到来,无法先做完整排序。

Kruskal's Algorithm 代码

简洁、可运行的 Kruskal's Algorithm 实现,提供 Python, JavaScript, Java, C++, C 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。

Kruskal's Algorithm 的 Python 代码

Python
1def find(parent, x):2    while parent[x] != x:3        parent[x] = parent[parent[x]]  # path compression4        x = parent[x]5    return x6
7
8def kruskal(vertices, edges):9    # Take the cheapest edge that does not close a cycle10    parent = {v: v for v in vertices}11    mst, total = [], 012    for w, u, v in sorted(edges):13        root_u, root_v = find(parent, u), find(parent, v)14        if root_u != root_v:15            parent[root_u] = root_v16            mst.append((u, v, w))17            total += w18    return mst, total19
20
21vertices = ["A", "B", "C", "D", "E"]22edges = [23    (4, "A", "B"), (1, "A", "C"), (3, "B", "C"), (2, "B", "D"),24    (5, "C", "D"), (6, "C", "E"), (7, "D", "E"),25]26
27mst, total = kruskal(vertices, edges)28for u, v, w in mst:29    print(f"{u} - {v} (weight {w})")30print("Total MST weight:", total)
在 Python 在线编辑器中运行这段代码

Kruskal 算法常见问题

最小生成树是什么?
连通带权图的最小生成树(MST)是边的一个子集,它连接所有节点、没有环,并且总权重尽可能小。对于 V 个节点,它恰好有 V - 1 条边。
Kruskal 算法和 Prim 算法有什么区别?

两者都用贪心策略构建最小生成树。Kruskal 对所有边做全局排序,借助并查集加入不会形成环的最便宜的边。Prim 从一个起始节点出发向外生长出一棵树,每次加入离开这棵树的最便宜的边。Kruskal 适合稀疏图;Prim(配合堆)适合稠密图。

Kruskal 算法为什么要用并查集?

加入一条边之前,Kruskal 必须检查它的两个端点是否已经连通,因为加入这样的边会形成环。并查集(不相交集合)能回答“它们在同一个分量里吗?”,并以接近常数的均摊时间合并分量,让算法保持高效。

什么时候该用 Kruskal 算法而不是 Prim 算法?
当图是稀疏的,而且你已经有一份可以排序的完整边列表时,优先用 Kruskal:E 较小时,O(E log E) 的排序开销很低。在 E 接近 V² 的稠密图上,使用二叉堆或斐波那契堆的 Prim 算法往往更快,因为它不需要事先把所有边排序。
Kruskal 算法能用于不连通的图吗?
可以。如果图不连通,Kruskal 就无法凑满 V - 1 条边,而是得到一个最小生成森林:每个连通分量各有一棵 MST。这是自然而然的结果,因为并查集永远不会合并彼此之间没有路径的节点。
实现 Kruskal 算法时最常见的错误是什么?

省略并查集的路径压缩和按秩合并,这会让每次连通性判断从接近常数时间退化到接近线性时间,甚至成为运行时间的主要部分。另一个常见 bug 是加入边之后忘了真正合并两个分量,导致后面的边形成环。

Coddy 编程语言插图

在 Coddy 掌握算法

开始