并查集(Union-Find)

Posted lwyeah

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集(Union-Find)相关的知识,希望对你有一定的参考价值。

一、基本操作:

1、Find:当且仅当两个元素属于相同的集合时,返回相同的名字

2、Union:将两个不相交的集合合并为一个不想交的集合。

应用:在等价关系中,判断两个元素之间是否有关系或者添加等价关系。

二、基本数据结构

1、Quick-Find实现

采用一个数组保存每个等价类的名字,这种实现下Find的复杂度为O(1),而Union实现的复杂度为O(n)。

例如Union(i,j):Union操作需要遍历整个数组,将所有为j的元素改为i。

 1 public class ADTQuickFind {
 2     private int[] id;
 3     private int size;
 4     public ADTQuickFind(int size){
 5         this.size = size;
 6         id = new int[size];
 7         //初始化默认所有节点不联通
 8         for(int i = 0; i < size; i++)
 9             id[i] = i;
10     }
11 
12     public int find(int p){
13         //p的值出现异常
14         if(p < 0 || p >= size)
15             return  -1;
16         return id[p];
17     }
18 
19     public void Union(int p, int q){
20         int pId = find(p);
21         int qId = find(q);
22         if(pId == qId)
23             return;
24         for(int i = 0; i < size; i++){
25             if(id[i] == qId)
26                 id[i] = pId;
27 
28         }
29     }
30 
31     public boolean isRelated(int p, int q){
32         return find(p) == find(q);
33     }
34 }

如果最开始所有元素都不连通,那么最多执行N-1次Union运算,此时所有的元素都在一个等价类中,总的时间复杂度为O(n2)。

如果Find运算比较多的情况下,存在Ω(n2)次Find运算,那么平均每次Find或者Union的时间复杂度为O(1),但是如果Find的次数比较少,那么这种方法是不可以接受的。

2、Quick-Union实现

在数组中隐式地存储一个森林,每个数组对应的位置存储对应节点的父节点的编号,树根节点保存自己的编号。

Find:由于Find操作要求当且仅当两个元素在相同的集合时,作用在两个元素上的Find操作返回相同的名字。而返回元素的标记是什么并不重要,因此通过返回一个节点的根节点的编号代表节点所处等价类的编号。

Union:使一个节点的根指针指向另一棵树的根节点。

 1 public class ADTQuickUnion {
 2     int[] parent;
 3     int size;
 4     public ADTQuickUnion(int size){
 5         this.size = size;
 6         parent = new int[size];
 7         for(int i = 0; i < size; i++)
 8             parent[i] = i;
 9     }
10     public int find(int p){
11         if(p < 0 || p >= size)
12             return -1;   //出现异常值
13         while(parent[p] != p)
14             p = parent[p];
15         return p;
16     }
17     public void union(int p, int q){
18         int pId = find(p);
19         int qId = find(q);
20         if(pId == qId)
21             return;
22         parent[qId] = pId;
23     }
24     public boolean isRelated(int p, int q){
25         return find(p) == find(q);
26     }
27 }

算法中执行Find操作花费的时间与节点的深度成正比,最坏情况下,会建立一个深度为N-1的树,使用一次Find的最坏情形运行时间为O(n)。

3、QuickUnionBetter实现

在Union操作中,将元素个数较少的树插到元素个数较多的树的根节点上。

 1 public class ADTQuickUnionBetter {
 2     private int[] parent;
 3     private int[] mSize;
 4     private int size;
 5     public ADTQuickUnionBetter(int size){
 6         this.size = size;
 7         parent = new int[size];
 8         mSize = new int[size];
 9         for(int i = 0; i < size; i++){
10             parent[i] = i;
11             mSize[i] = 1;
12         }
13     }
14     public int find(int p){
15         if(p < 0 || p >= size)
16             return -1;  //表明出现异常值
17         while(p != parent[p])
18             p = parent[p];
19         return p;
20     }
21     public void union(int p, int q){
22         int pId = find(p);
23         int qId = find(q);
24         if(pId == qId)
25             return;
26         if(mSize[pId] < mSize[qId]){
27             parent[pId] = qId;
28             mSize[qId] = mSize[pId] + mSize[qId];
29         }
30         else{
31             parent[qId] = pId;
32             mSize[pId] = mSize[qId] + mSize[pId];
33         }
34     }
35     public boolean isRelated(int p, int q){
36         return find(p) == find(q);
37     }
38 }

可以证明,任何节点的深度不会超过log(n),由于随着深度每次随着Union的结果而增大1时,该节点被置于节点数至少为原先两倍大的树上。

因此Find操作的时间复杂度为O(log(n)),连续M次操作的时间复杂度为O(MlogN),平均时间得到证明为O(M)时间,为线性的。

4、Find中优化路径压缩优化路径压缩

1 public int find(int p){
2     if(p < 0 || p >= size)
3         return -1;  //异常值
4     while(p != parent[p]){
5         parent[p] = parent[parent[p]];
6         p = parent[p];
7     }
8     return p;
9 }

 

以上是关于并查集(Union-Find)的主要内容,如果未能解决你的问题,请参考以下文章

Restructuring Company和Almost Union-Find 并查集的区间合并与并查集的删除

Union-Find 并查集算法

算法(第四版)之并查集(union-find算法)

Union-Find(并查集): Quick union improvements

132.1.001 Union-Find | 并查集

Almost Union-Find 并查集的删除