并查集
并查集是一种精巧实用的数据结构,它主要用于处理一些不相交集合的合并问题,一些常用的用途有求连通子图,求最小生成树的Kruskal算法和求最近公共祖先(LCA)等
并查集的基本操作主要有:
- 初始化init
- 查询find
- 合并union
简单实现
1.初始化。 假设编号有1、2、3...的n个元素,我们用数组father[]来存储每个元素的父节点,一开始我们将他们的父节点设为自己
func Init(n int) {
var father [math.MaxInt]int
for i:=1; i<=n; i++ {
father[i] = i
}
}
2.合并。 将元素合并成一个并查集里面去
func Union(i int, j int) {
iFather := father[i] //找到i的祖先节点
jFather := father[j] //找到j的祖先节点
father[iFather] = jFather //i的祖先指向j的祖先
}
将上图初始化后的五个元素,通过Union(4,3),Union(3,2),Union(2,1)合并元素为一个并查集,将4的父节点赋给3,将3的父节点赋给2,将2的父节点赋给1,此时1的父节点还是1本身,形成下图的结果
3.查询。 找到i的祖先直接返回(未进行路径压缩)
func Find(i int) int {
if father[i] == i { //递归出口,当找到了祖先节点时,就返回
return i
} else {
return Find(father[i]) //不断往上查找祖先节点
}
}
以上面合并后的并查集为例,当我们查找节点4的祖先节点时,先找到其父节点father[4]的值为3,不等于4,那么继续递归查找节点3的父节点......直到递归查找到节点1时,其father[1] == 1,返回节点4的祖先节点1。
优化:进行路径压缩后的查询
当我们继续给上图添加元素时Union(4,5),此时需要查找3次找到节点4的祖先节点1,然后将节点1的父节点赋给5,如果我们继续添加元素,那么查找元素祖先节点所需次数也是逐渐增加,影响了查询效率。
路径压缩
func Find(i int) int {
if i == father[i] {
return i
} else {
father[i] = Find(father[i]) //进行路径压缩,直接将元素的父节点设置为元素的祖先节点
return father[i] //返回父节点
}
}
经过路径压缩后的并查集
此时,当我们合并元素Union(4,5)时,只需查询一次就能找到节点4的祖先节点1,然后进行合并。经过路径压缩后即使我们合并10000个元素,此时也只需1步就能找到节点10000的祖先节点1,大大压缩了查询时间。
总结:所谓的路径压缩就是将纵向延展的并查集扁平化为横向扩展的模式
写一写
//并查集
type UnionSet struct {
Father []int
}
func (u UnionSet) Init(n int) []int {
//var father [math.MaxInt]int
for i := 1; i <= n; i++ {
u.Father[i] = i
}
return u.Father
}
func (u UnionSet) Union(i int, j int) {
iFather := u.Father[i] //找到i的祖先节点
jFather := u.Father[j] //找到j的祖先节点
u.Father[iFather] = jFather //i的祖先指向j的祖先
}
func (u UnionSet) Find(i int) int {
if i == u.Father[i] {
return i
} else {
u.Father[i] = u.Find(u.Father[i]) //进行路径压缩
return u.Father[i] //返回父节点
}
}
func main() {
tmp := [][]int{{10, 7}, {2, 4}, {5, 7}, {1, 3}, {8, 9}, {1, 2}, {5, 6}, {2, 3}, {3}, {3, 4}, {7, 10}, {8, 9}}
unionset := UnionSet{
Father: make([]int, tmp[0][0]+1),
}
unionset.Init(tmp[0][0])
for i := 1; i <= tmp[0][1]; i++ {
unionset.Union(tmp[i][0], tmp[i][1]) //将关系压入并查集
}
if len(tmp[tmp[0][1]+1]) != 1 {
return
} //{3}问询的数量
for i := tmp[0][1] + 2; i < len(tmp); i++ {
if unionset.Find(tmp[i][0]) == unionset.Find(tmp[i][1]) {
fmt.Println("Yes")
} else {
fmt.Println("No")
}
}
}
/*Yes
No
Yes*/