以下是 Kruskal 算法的 Python 实现,包含详细的注释:
python
class UnionFind:
"""并查集类,用于检测环路"""
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(n, edges):
"""
Kruskal算法求最小生成树
参数:
n: 节点数
edges: 边列表,格式 [(u, v, w), ...]
u,v为节点编号(0到n-1),w为权重
返回:
mst_edges: 最小生成树的边列表
total_weight: 最小生成树的总权重
"""
# 1. 按权重从小到大排序边
edges.sort(key=lambda x: x[2])
# 2. 初始化并查集
uf = UnionFind(n)
# 3. 初始化结果
mst_edges = []
total_weight = 0
edges_used = 0
# 4. 遍历所有边
for u, v, w in edges:
# 如果加入这条边不会形成环路
if uf.union(u, v):
mst_edges.append((u, v, w))
total_weight += w
edges_used += 1
# 当已经找到n-1条边时,最小生成树已构建完成
if edges_used == n - 1:
break
# 如果无法形成包含所有节点的生成树
if edges_used != n - 1:
return [], float('inf')
return mst_edges, total_weight
# 测试示例
if __name__ == "__main__":
# 示例图(邻接表表示)
# 节点数
n = 5
# 边列表: (u, v, weight)
edges = [
(0, 1, 2),
(0, 3, 6),
(1, 2, 3),
(1, 3, 8),
(1, 4, 5),
(2, 4, 7),
(3, 4, 9)
]
# 运行Kruskal算法
mst_edges, total_weight = kruskal(n, edges)
# 输出结果
print("最小生成树的边:")
for u, v, w in mst_edges:
print(f"{u} -- {v} (权重: {w})")
print(f"最小生成树总权重: {total_weight}")
# 可视化表示
print("\n最小生成树结构:")
# 创建邻接表表示
adj_list = [[] for _ in range(n)]
for u, v, w in mst_edges:
adj_list[u].append((v, w))
adj_list[v].append((u, w))
for i in range(n):
connections = ", ".join([f"{neighbor}({weight})" for neighbor, weight in adj_list[i]])
print(f"节点 {i}: {connections if connections else '无连接'}")
算法步骤说明:
-
初始化:
- 创建并查集用于检测环路
- 按权重对边进行排序
-
处理每条边:
- 从小到大遍历排序后的边
- 使用并查集检查当前边是否会形成环路
- 如果不形成环路,则加入最小生成树
-
终止条件:
- 当找到 n-1 条边时停止(n为节点数)
- 或者遍历完所有边
时间复杂度分析:
- 排序边:O(E log E)
- 并查集操作:近似 O(α(V)),其中α是阿克曼函数的反函数
- 总体:O(E log E)
使用示例:
python
# 自定义图的示例
n = 4
edges = [
(0, 1, 10),
(0, 2, 6),
(0, 3, 5),
(1, 3, 15),
(2, 3, 4)
]
mst_edges, total_weight = kruskal(n, edges)
print("最小生成树:")
for edge in mst_edges:
print(f"边 {edge[0]}-{edge[1]},权重: {edge[2]}")
print(f"总权重: {total_weight}")
这个实现包含了并查集的优化(路径压缩和按秩合并),确保算法的高效性。
以下是 Kruskal 算法的 Kotlin 实现:
kotlin
import java.util.*
class UnionFind(private val n: Int) {
private val parent = IntArray(n) { it }
private val rank = IntArray(n) { 0 }
/**
* 查找根节点(带路径压缩)
*/
fun find(x: Int): Int {
if (parent[x] != x) {
parent[x] = find(parent[x])
}
return parent[x]
}
/**
* 合并两个集合(按秩合并)
* @return 如果x和y原本不在同一集合中则返回true,否则返回false
*/
fun union(x: Int, y: Int): Boolean {
val rootX = find(x)
val rootY = find(y)
if (rootX == rootY) {
return false // 已经在同一集合中
}
// 按秩合并
when {
rank[rootX] < rank[rootY] -> parent[rootX] = rootY
rank[rootX] > rank[rootY] -> parent[rootY] = rootX
else -> {
parent[rootY] = rootX
rank[rootX]++
}
}
return true
}
}
data class Edge(
val u: Int, // 起始节点
val v: Int, // 终止节点
val weight: Int // 权重
)
/**
* Kruskal算法求最小生成树
* @param n 节点数量
* @param edges 边列表
* @return Pair(最小生成树的边列表, 总权重),如果无法形成生成树则返回null
*/
fun kruskal(n: Int, edges: List<Edge>): Pair<List<Edge>, Int>? {
// 1. 按权重从小到大排序边
val sortedEdges = edges.sortedBy { it.weight }
// 2. 初始化并查集
val uf = UnionFind(n)
// 3. 初始化结果
val mstEdges = mutableListOf<Edge>()
var totalWeight = 0
var edgesUsed = 0
// 4. 遍历所有边
for (edge in sortedEdges) {
// 如果加入这条边不会形成环路
if (uf.union(edge.u, edge.v)) {
mstEdges.add(edge)
totalWeight += edge.weight
edgesUsed++
// 当已经找到n-1条边时,最小生成树已构建完成
if (edgesUsed == n - 1) {
break
}
}
}
// 如果无法形成包含所有节点的生成树
return if (edgesUsed == n - 1) {
Pair(mstEdges, totalWeight)
} else {
null
}
}
/**
* 打印图结构
*/
fun printGraph(n: Int, edges: List<Edge>, title: String) {
println("\n=== $title ===")
// 创建邻接表
val adjList = Array(n) { mutableListOf<Pair<Int, Int>>() }
for (edge in edges) {
adjList[edge.u].add(Pair(edge.v, edge.weight))
adjList[edge.v].add(Pair(edge.u, edge.weight))
}
for (i in 0 until n) {
val connections = adjList[i].joinToString(", ") {
"${it.first}(${it.second})"
}
println("节点 $i: ${if (connections.isNotEmpty()) connections else "无连接"}")
}
}
fun main() {
// 示例1:与Python版本相同的图
println("示例1:")
val n1 = 5
val edges1 = listOf(
Edge(0, 1, 2),
Edge(0, 3, 6),
Edge(1, 2, 3),
Edge(1, 3, 8),
Edge(1, 4, 5),
Edge(2, 4, 7),
Edge(3, 4, 9)
)
printGraph(n1, edges1, "原始图")
val result1 = kruskal(n1, edges1)
if (result1 != null) {
val (mstEdges1, totalWeight1) = result1
println("\n最小生成树的边:")
mstEdges1.forEach { edge ->
println("${edge.u} -- ${edge.v} (权重: ${edge.weight})")
}
println("最小生成树总权重: $totalWeight1")
printGraph(n1, mstEdges1, "最小生成树")
} else {
println("无法形成最小生成树")
}
// 示例2:另一个图
println("\n\n示例2:")
val n2 = 4
val edges2 = listOf(
Edge(0, 1, 10),
Edge(0, 2, 6),
Edge(0, 3, 5),
Edge(1, 3, 15),
Edge(2, 3, 4)
)
printGraph(n2, edges2, "原始图")
val result2 = kruskal(n2, edges2)
if (result2 != null) {
val (mstEdges2, totalWeight2) = result2
println("\n最小生成树的边:")
mstEdges2.forEach { edge ->
println("边 ${edge.u}-${edge.v},权重: ${edge.weight}")
}
println("总权重: $totalWeight2")
printGraph(n2, mstEdges2, "最小生成树")
} else {
println("无法形成最小生成树")
}
// 示例3:无法形成生成树的情况
println("\n\n示例3: 测试不连通图")
val n3 = 5
val edges3 = listOf(
Edge(0, 1, 1),
Edge(2, 3, 2),
Edge(2, 4, 3)
// 节点0,1与其他节点不连通
)
val result3 = kruskal(n3, edges3)
if (result3 == null) {
println("图不连通,无法形成包含所有节点的生成树")
}
}
/**
* 实用扩展:从邻接矩阵创建边列表
*/
fun edgesFromAdjacencyMatrix(matrix: Array<IntArray>): List<Edge> {
val edges = mutableListOf<Edge>()
val n = matrix.size
for (i in 0 until n) {
for (j in i + 1 until n) {
if (matrix[i][j] > 0) {
edges.add(Edge(i, j, matrix[i][j]))
}
}
}
return edges
}
/**
* 实用扩展:从邻接表创建边列表
*/
fun edgesFromAdjacencyList(adjList: Array<List<Pair<Int, Int>>>): List<Edge> {
val edges = mutableListOf<Edge>()
val visited = mutableSetOf<String>()
for (u in adjList.indices) {
for ((v, weight) in adjList[u]) {
// 避免重复添加无向边
val key = if (u < v) "$u-$v" else "$v-$u"
if (!visited.contains(key)) {
edges.add(Edge(u, v, weight))
visited.add(key)
}
}
}
return edges
}
关键特性:
1. UnionFind 类
- 实现了带路径压缩和按秩合并的并查集
- 用于高效检测环路
2. Edge 数据类
- 使用 Kotlin 的
data class简洁表示边 - 自动生成
toString(),equals(),hashCode()等方法
3. kruskal 函数
- 返回
Pair<List<Edge>, Int>?,使用可空类型处理无法形成生成树的情况 - 使用 Kotlin 标准库的
sortedBy进行排序
4. 辅助函数
printGraph():可视化图结构edgesFromAdjacencyMatrix():从邻接矩阵转换edgesFromAdjacencyList():从邻接表转换
5. Kotlin 特性应用
- 使用
when表达式替代多重 if-else - 使用扩展函数增强实用性
- 使用
mutableListOf和joinToString等标准库函数
使用示例:
kotlin
// 简单用法
val n = 5
val edges = listOf(
Edge(0, 1, 2),
Edge(0, 2, 3),
Edge(1, 2, 1),
Edge(1, 3, 4),
Edge(2, 3, 5),
Edge(3, 4, 2)
)
val result = kruskal(n, edges)
result?.let { (mstEdges, totalWeight) ->
println("最小生成树找到 ${mstEdges.size} 条边")
println("总权重: $totalWeight")
} ?: println("图不连通")
时间复杂度:
- 排序边:O(E log E)
- 并查集操作:近似 O(α(V))
- 总体:O(E log E),适合稀疏图
这个 Kotlin 实现充分利用了 Kotlin 的语言特性,代码更简洁且类型安全。