问题背景
在图论中,连通分量(Connected Components)是指无向图中的极大连通子图。对于算法导论中的经典问题,我们需要证明:当 CONNECTED-COMPONENTS 算法处理完所有边后,两个顶点在相同的连通分量中,当且仅当它们在同一个集合中。
这通常通过并查集(Union-Find)数据结构来实现。下面我们先从理论层面梳理这个性质,再给出 Go 语言的完整实现。
理论证明
要理解这个结论,关键在于明确并查集的操作如何映射到图的连通性上。
必要性 如果两个顶点 u 和 v 在同一个连通分量中,根据定义,它们之间存在一条路径。这条路径上的每一条边都会被算法遍历到。每当遇到一条连接不同集合的边时,算法就会执行合并操作(Union)。因此,路径上的所有顶点最终都会被合并到同一个集合中,u 和 v 自然也在其中。
充分性 反之,如果 u 和 v 在同一个集合中,说明它们之间必然经历过至少一次合并操作。而合并操作的前提是存在一条边直接连接这两个顶点所在的集合。递归地看,这意味着 u 和 v 之间可以通过一系列被合并过的边相互到达,即存在路径,属于同一个连通分量。
Go 语言实现
为了高效验证这一性质,我们采用带有路径压缩和按秩合并优化的并查集。这种实现方式时间复杂度接近常数级,非常适合处理大规模图的连通性问题。
package main
import "fmt"
// UnionFind 结构体封装了并查集的核心逻辑
type UnionFind struct {
parent []int // 父节点数组
rank []int // 树的秩,用于优化合并深度
}
// NewUnionFind 初始化并查集,每个顶点初始为一个独立的集合
func NewUnionFind(n int) *UnionFind {
parent := make([]int, n)
rank := make([]int, n)
for i := 0; i < n; i++ {
parent[i] = i
rank[i] = 0
}
return &UnionFind{
parent: parent,
rank: rank,
}
}
// Find 查找元素 x 的根节点,包含路径压缩优化
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
// 路径压缩:将查找路径上的所有节点直接指向根节点
uf.parent[x] = uf.Find(uf.parent[x])
}
return uf.parent[x]
}
Union(x, y ) {
rootX := uf.Find(x)
rootY := uf.Find(y)
rootX != rootY {
uf.rank[rootX] > uf.rank[rootY] {
uf.parent[rootY] = rootX
} uf.rank[rootX] < uf.rank[rootY] {
uf.parent[rootX] = rootY
} {
uf.parent[rootY] = rootX
uf.rank[rootX]++
}
}
}
Connected(x, y ) {
uf.Find(x) == uf.Find(y)
}
{
numVertices :=
edges := [][]{{, }, {, }, {, }}
uf := NewUnionFind(numVertices)
_, edge := edges {
uf.Union(edge[], edge[])
}
fmt.Println(, uf.Connected(, ))
fmt.Println(, uf.Connected(, ))
fmt.Println(, uf.Connected(, ))
}


