今天,我将解析一段Java代码,该代码实现了Kruskal算法,用于在连通的无向图中找到最小生成树。
首先,我们来了解一些关键组件:
1. DisjointSet(不相交集):**
这是Kruskal算法中的辅助数据结构,用于管理不相交集的集合。
- Find(查找):确定特定元素属于哪个集合,并返回该集合的代表成员。
- Union(合并):将两个不同的集合合并为一个集合。
2. Kruskal:**
这个类包含实现Kruskal算法的方法。
- worst_case(最坏情况):该方法演示如何使用预定义的边集合来利用算法。
- Kruskal:该方法提供Kruskal算法的逻辑。
Kruskal算法的工作原理:
- 初始化:从每个顶点开始,构建一个由不相交的单节点树组成的森林。
- 排序:按照边的权重进行非递减排序。
- 添加边:对于每条边,按照非递减的顺序,如果边连接了两个不同的树,则将其添加到森林中,将两个树合并为一棵树。如果边连接了同一棵树的节点,则跳过它(以避免形成环)。
- 终止:算法在添加了(V-1)条边后结束,其中V是顶点的数量。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
// DisjointSet 类用于管理不相交集的集合
class DisjointSet {
private int[] parent; // 存储每个节点的父节点
private int[] rank; // 存储每个节点的秩(树的高度)
// 构造函数,初始化 DisjointSet
public DisjointSet(int n) {
parent = new int[n + 1]; // 考虑从1开始的节点索引
rank = new int[n + 1];
for (int i = 0; i <= n; i++) {
parent[i] = i; // 每个节点的父节点初始化为自身
rank[i] = 0; // 每个节点的秩初始化为0
}
}
// 查找方法,返回节点 x 所在集合的代表元素
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
// 合并方法,将 x 和 y 所在的集合合并,返回 true 如果合并成功,否则返回 false
public boolean union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return false; // 已经在同一个集合中
}
// 将秩较低的树的根节点指向秩较高的树的根节点
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;
}
}
public class Kruskal {
// 示例图的邻接矩阵表示
int[][] example_1 = new int[][] { { 0, 1, 3, Integer.MAX_VALUE, Integer.MAX_VALUE },
{ 1, 0, 3, 6, Integer.MAX_VALUE },
{ 3, 3, 0, 4, 2 },
{ Integer.MAX_VALUE, 6, 4, 0, 5 },
{ Integer.MAX_VALUE, Integer.MAX_VALUE, 2, 5, 0 } };
// worst_case 方法演示了如何使用预定义的边集合来利用 Kruskal 算法
public void worst_case() {
int n = 5; // 顶点数
int m = 7; // 边数
Set<Edge> E = new HashSet<>(); // 存储边的集合
// 添加边到集合
E.add(new Edge(1, 2, 1));
E.add(new Edge(1, 3, 3));
E.add(new Edge(2, 3, 3));
E.add(new Edge(2, 4, 6));
E.add(new Edge(3, 4, 4));
E.add(new Edge(3, 5, 2));
E.add(new Edge(4, 5, 5));
// 将 Set 转换为 List
List<Edge> edgeList = new ArrayList<>(E);
// 对 List 进行排序
edgeList.sort(Comparator.comparingInt(Edge::getWeight));
// 创建一个不相交集
DisjointSet disjointSet = new DisjointSet(n);
// 创建一个列表来存储 MST 的边
List<Edge> mst = new ArrayList<>();
// 按排序顺序遍历所有边
for (Edge edge : edgeList) {
// 如果边不形成环,将其添加到 MST 中
if (disjointSet.union(edge.getU(), edge.getV())) {
mst.add(edge);
}
}
// 显示 MST 中的边
System.out.println("Minimum Spanning Tree: ");
for (Edge edge : mst) {
System.out.println("From: " + edge.getU() + " To: " + edge.getV() + " Weight: " + edge.getWeight());
}
}
// kruskal 方法提供了 Kruskal 算法的逻辑
public Set<Edge> kruskal(int n, int m, Set<Edge> E) {
int i, j;
Set<Edge> P = new HashSet<>();
Set<Edge> Q = new HashSet<>();
Edge e = new Edge(0, 0, 0);
Set<Edge> F = new HashSet<>();
// 当 F 的大小小于 (n - 1) 时,继续循环
while (F.size() < (n - 1)) {
// 将 E 中的边移动到 P 中
for (i = 0; i < m; i++) {
e = E.iterator().next();
E.remove(e);
P.add(e);
}
// 从 P 中选择一条边,如果它不连接同一个顶点,将其添加到 F 中
for (i = 0; i < m; i++) {
e = P.iterator().next();
P.remove(e);
if (e.getU() != e.getV()) {
F.add(e);
break;
} else {
Q.add(e);
}
}
// 将 Q 中的边移回 P 中
for (i = 0; i < m; i++) {
e = Q.iterator().next();
Q.remove(e);
P.add(e);
}
}
return F;
}
}
代码深入解析:
在Kruskal
类中:
- worst_case方法:该方法首先初始化一组边,然后根据它们的权重对这些边进行排序。然后,代码遍历这些边,如果它们不形成环,则将它们添加到最小生成树中。
- Kruskal方法:这个方法看起来更复杂,有多个循环和集合
P
、Q
和F
。
然而,提供的方法并不遵循传统的Kruskal算法。相反,它展示了通过重复迭代边来构建最小生成树的另一种方法。