go实现一个并查集

1,094 阅读3分钟
并查集

并查集是一种精巧实用的数据结构,它主要用于处理一些不相交集合的合并问题,一些常用的用途有求连通子图,求最小生成树的Kruskal算法和求最近公共祖先(LCA)等

并查集的基本操作主要有:

  1. 初始化init
  2. 查询find
  3. 合并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
   }
}

image.png

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本身,形成下图的结果

image.png

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]   //返回父节点
   }
}

经过路径压缩后的并查集

image.png

此时,当我们合并元素Union(4,5)时,只需查询一次就能找到节点4的祖先节点1,然后进行合并。经过路径压缩后即使我们合并10000个元素,此时也只需1步就能找到节点10000的祖先节点1,大大压缩了查询时间。

总结:所谓的路径压缩就是将纵向延展的并查集扁平化为横向扩展的模式

写一写

image.png

//并查集
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*/