7-5 普里姆最小成本生成树算法
克鲁斯卡尔最小生成树算法(Kruskal's Minimum Cost Spanning Tree)
克鲁斯卡尔算法(Kruskal's Algorithm)是一种求解最小生成树(Minimum Spanning Tree,简称 MST)的经典贪心算法(Greedy Algorithm)。其核心思想是:将图中所有边按权重从小到大排序,然后依次考虑每条边——如果这条边连接的两个顶点属于不同的连通分量,就将这条边加入生成树中;否则跳过。使用并查集(Union-Find / Disjoint Set Union,简称 DSU)数据结构来高效判断两个顶点是否属于同一连通分量。
Kruskal 算法的贪心策略直觉上很自然:每次都选当前最短的且不会形成环的边,最终所有选中的边恰好构成一棵连接所有顶点的总权重最小的树。
本文使用如下加权无向图(9 个顶点,14 条边):
边列表(按权重排序):
(0,1,4) (0,7,8) (1,2,8) (1,7,11) (2,3,7) (2,5,4)
(2,8,2) (3,4,9) (3,5,14) (4,5,10) (5,6,2) (6,7,1)
(6,8,6) (7,8,7)
格式: (u, v, weight)
图的结构:
0---4---1---8---2
| /| / |
8 11 8| 7/ |2
| / | / |
7 | 3 8
| |/ |
1---6---5---2---+
| /|
6 14 10
| / |
6 4
7 5
MST 权重 = 37
MST 边: (6,7,1) (2,8,2) (5,6,2) (0,1,4) (2,5,4) (2,3,7) (0,7,8) (3,4,9)
边的表示与排序
Kruskal 算法的输入是一组边(Edge),每条边由两个端点和权重组成。算法的第一步是将所有边按权重从小到大排序。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Edge {
int u, v, weight;
};
// Sort edges by weight (ascending)
bool compareEdges(const Edge& a, const Edge& b) {
return a.weight < b.weight;
}
int main() {
vector<Edge> edges = {
{0, 1, 4}, {0, 7, 8}, {1, 2, 8}, {1, 7, 11},
{2, 3, 7}, {2, 5, 4}, {2, 8, 2}, {3, 4, 9},
{3, 5, 14}, {4, 5, 10}, {5, 6, 2}, {6, 7, 1},
{6, 8, 6}, {7, 8, 7},
};
// Sort edges by weight
sort(edges.begin(), edges.end(), compareEdges);
cout << "Edges sorted by weight:" << endl;
for (const Edge& e : edges) {
cout << "(" << e.u << "," << e.v << "," << e.weight << ")" << endl;
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int u, v, weight;
} Edge;
// Compare function for qsort (ascending by weight)
int compareEdges(const void* a, const void* b) {
Edge* ea = (Edge*)a;
Edge* eb = (Edge*)b;
return ea->weight - eb->weight;
}
int main() {
Edge edges[] = {
{0, 1, 4}, {0, 7, 8}, {1, 2, 8}, {1, 7, 11},
{2, 3, 7}, {2, 5, 4}, {2, 8, 2}, {3, 4, 9},
{3, 5, 14}, {4, 5, 10}, {5, 6, 2}, {6, 7, 1},
{6, 8, 6}, {7, 8, 7},
};
int edgeCount = 14;
// Sort edges by weight using qsort
qsort(edges, edgeCount, sizeof(Edge), compareEdges);
printf("Edges sorted by weight:\n");
for (int i = 0; i < edgeCount; i++) {
printf("(%d,%d,%d)\n", edges[i].u, edges[i].v, edges[i].weight);
}
return 0;
}
def main():
edges = [
(0, 1, 4), (0, 7, 8), (1, 2, 8), (1, 7, 11),
(2, 3, 7), (2, 5, 4), (2, 8, 2), (3, 4, 9),
(3, 5, 14), (4, 5, 10), (5, 6, 2), (6, 7, 1),
(6, 8, 6), (7, 8, 7),
]
# Sort edges by weight (3rd element)
edges.sort(key=lambda e: e[2])
print("Edges sorted by weight:")
for e in edges:
print(f"({e[0]},{e[1]},{e[2]})")
if __name__ == "__main__":
main()
package main
import (
"fmt"
"sort"
)
type Edge struct {
U, V, Weight int
}
func main() {
edges := []Edge{
{0, 1, 4}, {0, 7, 8}, {1, 2, 8}, {1, 7, 11},
{2, 3, 7}, {2, 5, 4}, {2, 8, 2}, {3, 4, 9},
{3, 5, 14}, {4, 5, 10}, {5, 6, 2}, {6, 7, 1},
{6, 8, 6}, {7, 8, 7},
}
// Sort edges by weight using sort.Slice
sort.Slice(edges, func(i, j int) bool {
return edges[i].Weight < edges[j].Weight
})
fmt.Println("Edges sorted by weight:")
for _, e := range edges {
fmt.Printf("(%d,%d,%d)\n", e.U, e.V, e.Weight)
}
}
上述代码将所有边按权重从小到大排序。C++ 使用 sort() 配合自定义比较函数;C 语言使用 qsort() 标准库函数;Python 使用 list.sort(key=...) 按 third 元素排序;Go 使用 sort.Slice() 配合匿名比较函数。排序是 Kruskal 算法的第一步,保证了贪心策略按权重递增的顺序选边。
运行该程序将输出:
Edges sorted by weight:
(6,7,1)
(2,8,2)
(5,6,2)
(0,1,4)
(2,5,4)
(6,8,6)
(2,3,7)
(7,8,7)
(0,7,8)
(3,4,9)
(4,5,10)
(1,2,8)
(1,7,11)
(3,5,14)
并查集(Union-Find)实现
并查集(Union-Find / Disjoint Set Union,简称 DSU)是 Kruskal 算法的关键数据结构。它支持两个核心操作:
- Find(x):查找元素 x 所属集合的代表元素(根节点)
- Union(x, y):将元素 x 和 y 所属的两个集合合并
为了提高效率,使用两种优化技术:
- 路径压缩(Path Compression):在 Find 操作中,将沿途经过的所有节点直接指向根节点,使后续查询更快
- 按秩合并(Union by Rank):在 Union 操作中,将高度较小的树挂到高度较大的树下,避免树退化成链表
经过这两种优化,每次操作的时间复杂度接近 O(1)(准确说是 O(alpha(n)),其中 alpha 是反阿克曼函数(Inverse Ackermann Function),增长极其缓慢)。
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
class UnionFind {
private:
vector<int> parent;
vector<int> rank;
public:
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0);
// Each node is its own parent initially
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
// Find with path compression
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // Path compression
}
return parent[x];
}
// Union by rank
bool unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return false; // Already in the same set
}
// Attach smaller rank tree under root of larger rank tree
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
return true;
}
};
int main() {
UnionFind uf(5);
// Test union and find operations
uf.unionSets(0, 1);
uf.unionSets(2, 3);
uf.unionSets(0, 2);
cout << "find(0) = " << uf.find(0) << endl;
cout << "find(1) = " << uf.find(1) << endl;
cout << "find(2) = " << uf.find(2) << endl;
cout << "find(3) = " << uf.find(3) << endl;
cout << "find(4) = " << uf.find(4) << endl;
cout << "0 and 3 in same set: " << (uf.find(0) == uf.find(3) ? "yes" : "no") << endl;
cout << "0 and 4 in same set: " << (uf.find(0) == uf.find(4) ? "yes" : "no") << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int* parent;
int* rank;
int n;
} UnionFind;
UnionFind* ufCreate(int n) {
UnionFind* uf = (UnionFind*)malloc(sizeof(UnionFind));
uf->n = n;
uf->parent = (int*)malloc(n * sizeof(int));
uf->rank = (int*)calloc(n, sizeof(int));
// Each node is its own parent initially
for (int i = 0; i < n; i++) {
uf->parent[i] = i;
}
return uf;
}
// Find with path compression
int ufFind(UnionFind* uf, int x) {
if (uf->parent[x] != x) {
uf->parent[x] = ufFind(uf, uf->parent[x]); // Path compression
}
return uf->parent[x];
}
// Union by rank, returns true if merged
int ufUnion(UnionFind* uf, int x, int y) {
int rootX = ufFind(uf, x);
int rootY = ufFind(uf, y);
if (rootX == rootY) {
return 0; // Already in the same set
}
// Attach smaller rank tree under root of larger rank tree
if (uf->rank[rootX] < uf->rank[rootY]) {
uf->parent[rootX] = rootY;
} else if (uf->rank[rootX] > uf->rank[rootY]) {
uf->parent[rootY] = rootX;
} else {
uf->parent[rootY] = rootX;
uf->rank[rootX]++;
}
return 1;
}
void ufFree(UnionFind* uf) {
free(uf->parent);
free(uf->rank);
free(uf);
}
int main() {
UnionFind* uf = ufCreate(5);
ufUnion(uf, 0, 1);
ufUnion(uf, 2, 3);
ufUnion(uf, 0, 2);
printf("find(0) = %d\n", ufFind(uf, 0));
printf("find(1) = %d\n", ufFind(uf, 1));
printf("find(2) = %d\n", ufFind(uf, 2));
printf("find(3) = %d\n", ufFind(uf, 3));
printf("find(4) = %d\n", ufFind(uf, 4));
printf("0 and 3 in same set: %s\n",
ufFind(uf, 0) == ufFind(uf, 3) ? "yes" : "no");
printf("0 and 4 in same set: %s\n",
ufFind(uf, 0) == ufFind(uf, 4) ? "yes" : "no");
ufFree(uf);
return 0;
}
Python 实现
class UnionFind:
"""Union-Find with path compression and union by rank."""
def __init__(self, n):
self.parent = list(range(n)) # Each node is its own parent
self.rank = [0] * n
def find(self, x):
"""Find with path compression."""
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Path compression
return self.parent[x]
def union(self, x, y):
"""Union by rank, returns True if merged."""
root_x = self.find(x)
root_y = self.find(y)
if root_x == root_y:
return False # Already in the same set
# Attach smaller rank tree under root of larger rank tree
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
return True
if __name__ == "__main__":
uf = UnionFind(5)
uf.union(0, 1)
uf.union(2, 3)
uf.union(0, 2)
print(f"find(0) = {uf.find(0)}")
print(f"find(1) = {uf.find(1)}")
print(f"find(2) = {uf.find(2)}")
print(f"find(3) = {uf.find(3)}")
print(f"find(4) = {uf.find(4)}")
print("0 and 3 in same set:", "yes" if uf.find(0) == uf.find(3) else "no")
print("0 and 4 in same set:", "yes" if uf.find(0) == uf.find(4) else "no")
Go 实现
package main
import "fmt"
type UnionFind struct {
parent []int
rank []int
}
func NewUnionFind(n int) *UnionFind {
uf := &UnionFind{
parent: make([]int, n),
rank: make([]int, n),
}
// Each node is its own parent initially
for i := range uf.parent {
uf.parent[i] = i
}
return uf
}
// Find with path compression
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.Find(uf.parent[x]) // Path compression
}
return uf.parent[x]
}
// Union by rank, returns true if merged
func (uf *UnionFind) Union(x, y int) bool {
rootX := uf.Find(x)
rootY := uf.Find(y)
if rootX == rootY {
return false // Already in the same set
}
// Attach smaller rank tree under root of larger rank tree
if uf.rank[rootX] < uf.rank[rootY] {
uf.parent[rootX] = rootY
} else if uf.rank[rootX] > uf.rank[rootY] {
uf.parent[rootY] = rootX
} else {
uf.parent[rootY] = rootX
uf.rank[rootX]++
}
return true
}
func main() {
uf := NewUnionFind(5)
uf.Union(0, 1)
uf.Union(2, 3)
uf.Union(0, 2)
fmt.Printf("find(0) = %d\n", uf.Find(0))
fmt.Printf("find(1) = %d\n", uf.Find(1))
fmt.Printf("find(2) = %d\n", uf.Find(2))
fmt.Printf("find(3) = %d\n", uf.Find(3))
fmt.Printf("find(4) = %d\n", uf.Find(4))
same03 := "no"
if uf.Find(0) == uf.Find(3) {
same03 = "yes"
}
fmt.Println("0 and 3 in same set:", same03)
same04 := "no"
if uf.Find(0) == uf.Find(4) {
same04 = "yes"
}
fmt.Println("0 and 4 in same set:", same04)
}
上述代码实现了带路径压缩和按秩合并优化的并查集。find 函数在查找根节点的过程中,将沿途经过的所有节点直接指向根节点(路径压缩),使后续查询近乎 O(1)。union 函数在合并两个集合时,将秩较小的树挂在秩较大的树下(按秩合并),避免树退化为链表。初始化时每个节点自成一个集合(parent[i] = i)。测试演示了三次合并操作后各节点的归属关系。
运行该程序将输出:
find(0) = 0
find(1) = 0
find(2) = 0
find(3) = 0
find(4) = 4
0 and 3 in same set: yes
0 and 4 in same set: no
Kruskal 算法实现
Kruskal 算法的完整步骤如下:
- 将图中所有边按权重从小到大排序
- 初始化并查集,每个顶点各自为一个集合
- 按排序顺序遍历每条边 (u, v, weight):
- 如果 u 和 v 属于不同的集合(
find(u) != find(v)),则将这条边加入 MST,并合并 u 和 v 所在的集合 - 如果 u 和 v 已在同一集合中,跳过该边(加入会形成环)
- 如果 u 和 v 属于不同的集合(
- 当 MST 包含 V-1 条边时停止
Kruskal 算法执行过程:
已排序的边(按权重):
(6,7,1) (2,8,2) (5,6,2) (0,1,4) (2,5,4) (6,8,6) (2,3,7) (7,8,7) (0,7,8) ...
逐步选边:
1. (6,7,1): find(6)!=find(7) → 加入MST, union(6,7) MST总重=1
2. (2,8,2): find(2)!=find(8) → 加入MST, union(2,8) MST总重=3
3. (5,6,2): find(5)!=find(6) → 加入MST, union(5,6) MST总重=5
4. (0,1,4): find(0)!=find(1) → 加入MST, union(0,1) MST总重=9
5. (2,5,4): find(2)!=find(5) → 加入MST, union(2,5) MST总重=13
6. (6,8,6): find(6)==find(8) → 跳过(会形成环)
7. (2,3,7): find(2)!=find(3) → 加入MST, union(2,3) MST总重=20
8. (7,8,7): find(7)==find(8) → 跳过(会形成环)
9. (0,7,8): find(0)!=find(7) → 加入MST, union(0,7) MST总重=28
10.(3,4,9): find(3)!=find(4) → 加入MST, union(3,4) MST总重=37
已选 8 条边 = V-1 = 9-1 = 8, 停止
MST 总权重 = 37
C++ 实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Edge {
int u, v, weight;
};
bool compareEdges(const Edge& a, const Edge& b) {
return a.weight < b.weight;
}
class UnionFind {
private:
vector<int> parent;
vector<int> rnk;
public:
UnionFind(int n) {
parent.resize(n);
rnk.resize(n, 0);
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
bool unionSets(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry) return false;
if (rnk[rx] < rnk[ry]) parent[rx] = ry;
else if (rnk[rx] > rnk[ry]) parent[ry] = rx;
else { parent[ry] = rx; rnk[rx]++; }
return true;
}
};
void kruskal(int V, vector<Edge>& edges) {
// Step 1: Sort edges by weight
sort(edges.begin(), edges.end(), compareEdges);
UnionFind uf(V);
vector<Edge> mst;
int totalWeight = 0;
// Step 2: Iterate sorted edges
for (const Edge& e : edges) {
if (uf.unionSets(e.u, e.v)) {
mst.push_back(e);
totalWeight += e.weight;
}
// MST has V-1 edges
if ((int)mst.size() == V - 1) break;
}
// Print result
cout << "Edges in MST:" << endl;
for (const Edge& e : mst) {
cout << " (" << e.u << "," << e.v << "," << e.weight << ")" << endl;
}
cout << "Total MST weight: " << totalWeight << endl;
}
int main() {
int V = 9;
vector<Edge> edges = {
{0, 1, 4}, {0, 7, 8}, {1, 2, 8}, {1, 7, 11},
{2, 3, 7}, {2, 5, 4}, {2, 8, 2}, {3, 4, 9},
{3, 5, 14}, {4, 5, 10}, {5, 6, 2}, {6, 7, 1},
{6, 8, 6}, {7, 8, 7},
};
kruskal(V, edges);
return 0;
}
C 实现
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int u, v, weight;
} Edge;
typedef struct {
int* parent;
int* rank;
int n;
} UnionFind;
UnionFind* ufCreate(int n) {
UnionFind* uf = (UnionFind*)malloc(sizeof(UnionFind));
uf->n = n;
uf->parent = (int*)malloc(n * sizeof(int));
uf->rank = (int*)calloc(n, sizeof(int));
for (int i = 0; i < n; i++) uf->parent[i] = i;
return uf;
}
int ufFind(UnionFind* uf, int x) {
if (uf->parent[x] != x) uf->parent[x] = ufFind(uf, uf->parent[x]);
return uf->parent[x];
}
int ufUnion(UnionFind* uf, int x, int y) {
int rx = ufFind(uf, x), ry = ufFind(uf, y);
if (rx == ry) return 0;
if (uf->rank[rx] < uf->rank[ry]) uf->parent[rx] = ry;
else if (uf->rank[rx] > uf->rank[ry]) uf->parent[ry] = rx;
else { uf->parent[ry] = rx; uf->rank[rx]++; }
return 1;
}
void ufFree(UnionFind* uf) {
free(uf->parent);
free(uf->rank);
free(uf);
}
int compareEdges(const void* a, const void* b) {
return ((Edge*)a)->weight - ((Edge*)b)->weight;
}
void kruskal(int V, Edge edges[], int edgeCount) {
// Step 1: Sort edges by weight
qsort(edges, edgeCount, sizeof(Edge), compareEdges);
UnionFind* uf = ufCreate(V);
Edge mst[14];
int mstCount = 0;
int totalWeight = 0;
// Step 2: Iterate sorted edges
for (int i = 0; i < edgeCount; i++) {
if (ufUnion(uf, edges[i].u, edges[i].v)) {
mst[mstCount++] = edges[i];
totalWeight += edges[i].weight;
}
if (mstCount == V - 1) break;
}
// Print result
printf("Edges in MST:\n");
for (int i = 0; i < mstCount; i++) {
printf(" (%d,%d,%d)\n", mst[i].u, mst[i].v, mst[i].weight);
}
printf("Total MST weight: %d\n", totalWeight);
ufFree(uf);
}
int main() {
int V = 9;
Edge edges[] = {
{0, 1, 4}, {0, 7, 8}, {1, 2, 8}, {1, 7, 11},
{2, 3, 7}, {2, 5, 4}, {2, 8, 2}, {3, 4, 9},
{3, 5, 14}, {4, 5, 10}, {5, 6, 2}, {6, 7, 1},
{6, 8, 6}, {7, 8, 7},
};
int edgeCount = sizeof(edges) / sizeof(edges[0]);
kruskal(V, edges, edgeCount);
return 0;
}
Python 实现
class UnionFind:
"""Union-Find with path compression and union by rank."""
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x == root_y:
return False
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
return True
def kruskal(V, edges):
"""Kruskal's algorithm for Minimum Spanning Tree."""
# Step 1: Sort edges by weight
edges.sort(key=lambda e: e[2])
uf = UnionFind(V)
mst = []
total_weight = 0
# Step 2: Iterate sorted edges
for u, v, w in edges:
if uf.union(u, v):
mst.append((u, v, w))
total_weight += w
if len(mst) == V - 1:
break
# Print result
print("Edges in MST:")
for u, v, w in mst:
print(f" ({u},{v},{w})")
print(f"Total MST weight: {total_weight}")
if __name__ == "__main__":
V = 9
edges = [
(0, 1, 4), (0, 7, 8), (1, 2, 8), (1, 7, 11),
(2, 3, 7), (2, 5, 4), (2, 8, 2), (3, 4, 9),
(3, 5, 14), (4, 5, 10), (5, 6, 2), (6, 7, 1),
(6, 8, 6), (7, 8, 7),
]
kruskal(V, edges)
Go 实现
package main
import (
"fmt"
"sort"
)
type Edge struct {
U, V, Weight int
}
type UnionFind struct {
parent []int
rank []int
}
func NewUnionFind(n int) *UnionFind {
uf := &UnionFind{
parent: make([]int, n),
rank: make([]int, n),
}
for i := range uf.parent {
uf.parent[i] = i
}
return uf
}
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.Find(uf.parent[x])
}
return uf.parent[x]
}
func (uf *UnionFind) Union(x, y int) bool {
rx, ry := uf.Find(x), uf.Find(y)
if rx == ry {
return false
}
if uf.rank[rx] < uf.rank[ry] {
uf.parent[rx] = ry
} else if uf.rank[rx] > uf.rank[ry] {
uf.parent[ry] = rx
} else {
uf.parent[ry] = rx
uf.rank[rx]++
}
return true
}
func kruskal(V int, edges []Edge) {
// Step 1: Sort edges by weight
sort.Slice(edges, func(i, j int) bool {
return edges[i].Weight < edges[j].Weight
})
uf := NewUnionFind(V)
var mst []Edge
totalWeight := 0
// Step 2: Iterate sorted edges
for _, e := range edges {
if uf.Union(e.U, e.V) {
mst = append(mst, e)
totalWeight += e.Weight
}
if len(mst) == V-1 {
break
}
}
// Print result
fmt.Println("Edges in MST:")
for _, e := range mst {
fmt.Printf(" (%d,%d,%d)\n", e.U, e.V, e.Weight)
}
fmt.Printf("Total MST weight: %d\n", totalWeight)
}
func main() {
V := 9
edges := []Edge{
{0, 1, 4}, {0, 7, 8}, {1, 2, 8}, {1, 7, 11},
{2, 3, 7}, {2, 5, 4}, {2, 8, 2}, {3, 4, 9},
{3, 5, 14}, {4, 5, 10}, {5, 6, 2}, {6, 7, 1},
{6, 8, 6}, {7, 8, 7},
}
kruskal(V, edges)
}
上述代码实现了完整的 Kruskal 最小生成树算法。首先将所有边按权重排序,然后使用并查集依次检查每条边:如果边的两个端点属于不同的集合,说明加入该边不会形成环,将其加入 MST 并合并两个集合;如果两个端点已在同一集合中,则跳过该边。当 MST 中包含 V-1 条边时算法结束。四种语言的实现逻辑完全一致,区别仅在于语言特性:C++ 使用结构体和类;C 语言使用函数和手动内存管理;Python 使用类和列表;Go 使用结构体和方法。
运行该程序将输出:
Edges in MST:
(6,7,1)
(2,8,2)
(5,6,2)
(0,1,4)
(2,5,4)
(2,3,7)
(0,7,8)
(3,4,9)
Total MST weight: 37
Kruskal 算法的性质
下表总结了 Kruskal 算法的时间和空间复杂度:
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度(Time Complexity) | O(E log E) | 排序 O(E log E),并查集操作 O(E alpha(V)),整体由排序主导 |
| 空间复杂度(Space Complexity) | O(V + E) | 存储边列表 O(E),并查集 O(V),MST 结果 O(V) |
其中 V 是顶点数(Vertex Count),E 是边数(Edge Count),alpha 是反阿克曼函数(Inverse Ackermann Function)。
Kruskal 算法的关键性质:
- 贪心正确性:Kruskal 算法基于贪心策略,每次选择权重最小且不形成环的边。该贪心策略的正确性由 cut property(切割性质)保证:对于图的任意一个割(Cut),横跨该割的最小权重边一定属于某棵最小生成树。
- 基于边而非顶点:Kruskal 从边的角度出发,不依赖起始顶点的选择,适合边数较少的稀疏图(Sparse Graph)。
- 环检测是核心:Kruskal 的关键操作是判断加入一条边是否会形成环。并查集通过判断两个端点是否在同一集合来高效完成这一操作。
- 结果唯一性:最小生成树的总权重是唯一的,但如果图中存在权重相同的边,可能有多棵不同的最小生成树。
Kruskal 与 Prim 算法的对比:
| 特性 | Kruskal | Prim |
|---|---|---|
| 策略 | 按边权排序,贪心选边 | 从一个顶点出发,贪心扩展 |
| 数据结构 | 并查集(Union-Find) | 优先队列(Priority Queue) |
| 时间复杂度 | O(E log E) | O((V+E) log V) |
| 适用场景 | 稀疏图(Sparse Graph) | 稠密图(Dense Graph) |
| 实现方式 | 基于边 | 基于顶点 |
Kruskal 算法的典型应用场景:
| 应用场景 | 说明 |
|---|---|
| 网络设计(Network Design) | 设计成本最低的通信网络、管道网络 |
| 集群分析(Cluster Analysis) | 单链接聚类(Single-linkage Clustering)的基础 |
| 电路设计(Circuit Design) | 最小化电路布线的总长度 |
| 逼近旅行商问题(TSP Approximation) | MST 权重是 TSP 最优解的下界 |

浙公网安备 33010602011771号