2019-数据结构-数据结构7.1-The Disjoint Set

The Disjoint Set ADT(不相交集,并查集)

  1. 使用来表示离散中的等价类和等价关系的表示。

1. 等价类(Equivalence Class)

  1. 等价类的定义:Suppose we have a set U={1,2,…,n} of n elements and a set R={(i1,j1), (i2,j2)…(ir,jr)}of r relations. The relation R is an equivalence relation iff the following conditions are true(symbol ’≡’ represent the equivalence relation on sets, x,y,z are elements in set):(假设我们有一个n个元素组成的集合U = {1,2,…,n},一个有r个关系的集合R.当切仅当以下条件成立的时候,R才是一个等价类)
    1. Reflexive x ≡ x.(自反性)
    2. Symmetric x ≡ y,y ≡ x(对称性)
    3. Transitive x ≡ y and y ≡ z,then x ≡ z(传递性)
  2. Eg.


2. 并查集提供的功能

  1. Combine(a,b):combine the equivalence classes that contains elements a and b into a single class(Combine(a,b):合并包含元素a和b的两个等价类为一个等价类)
  2. Find(e):determine the class that currently contains element e.(Find(e):找到包含元素e的等价类)

2.1. Combine(a,b)合并

  1. Combine(a,b) is equivalent to i=Find(a); j=Find(b); if(i!=j) Union(i,j);

3. 并查集的物理实现

  1. 并查集的物理实现是通过森林来表示。

  1. parent数组中存储的值为0的时候,这个结点表示为根结点
  2. 所以这个更快速的支持从下向上查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//simple tree solution to union-find problem 
//使用简单的树结构解决并集的查找问题
void Initialize(int n){
parent=new int[n+1];
for(int e=1;e<=n;e++) parent[e]=0;
}
int Find(int e) {
//向上找到其根结点
while(parent[e]) e=parent[e];
return e;
}
void Union(int i, int j) {
//合并两个结点
parent[j]=i;
}

3.1. Union的实现

  1. 实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DisjSets {
public DisjSets( int numElements )
public void union( int root1, int root2 )
public int find( int x ) private int [] s;
}
//并查集的构造方法
public DisjSets( int numElements ) {
s = new int [numElements];
for( int i = 0; i < s.length; i++ )
s[i] = -1; //一个根结点
}
//并查集的合并
public void union( int root1, int root2 ) {
s[root2] = root1;
}
//并查集的查找,使用递归完成
public int find( int x ) {
if( s[x] < 0 )
return x;
else
return find( s[x] );
}

3.2. 性能估计

  1. Time complexity:(算法复杂度)
    • Find-- O(h), h 是指树高
    • Union-- θ(1)
  2. Assume that u times unions and f times finds are to be performed, f>u, in the worst case a tree with m elements can have a height of m: Union(2,1),Union(3,2),Union(4,3),Union(5,4)…(假设我们进行u次组合操作和f次查找操作,f>u,最坏情况下的一颗有m个元素的树可以有高度m)
    • 严重不平衡的树,会影响到查找的时间复杂度

3.3. 性能提升

3.3.1. 方法一

  1. Weight rule: if the number of nodes in tree i is less than the number in tree j, then make j the parent of i; otherwise,make i the parent of j.(点数原则:如果 i 树的点数小于 j 树的点数,那么我们让 j 成为 i 的parent,反之亦然)
  2. 结点数少的树挂到结点多的树下面

3.3.2. 高度问题的实现

  1. 为了实现我们新建一个bool类型数组来记录是否是根节点。
  2. Besides the parent field, each node has a boolean field root .The root field is true iff the node is presently a root node.The parent field of each root node is used to keep a count of the total number of nodes in the tree.(除了父字段外,每个节点都有一个布尔字段根。如果当前节点是根节点,则根字段为真。每个根节点的父字段用于统计树中的节点总数。)
    • 也就是单独使用了一个布尔数组来实现是否为根。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Union with the weight rule
void Initialize(int n) {
root=new bool[n+1];
parent=new int[n+1];
for(int e=1;e<=n;e++) {
parent[e]=1;
root[e]=true;
}
}
int Find(int e) {
while(!root[e])
e=parent[e];
return e;
}
void Union(int i, int j) {
if(parent[i]<parent[j])
//i becomes subtree of j
{
parent[j]=parent[j]+parent[i];
root[i]=false;
parent[i]=j;
} else {
parent[i]=parent[i]+parent[j];
root[j]=false;
parent[j]=i;
}
}
  1. 如何省略去标记根的数组?
    • 使用负数来记录树高
1
2
3
4
5
6
7
8
9
10
//java
public void union( int root1, int root2 ) {
if(s[root2] < s[root1])
s[root1] = root2;
else {
if(s[root1] == s[root2] )
s[root1]--;
s[root2] = root1;
}
}//注意到负数会都反过来

  1. 例子如下

3.3.3. 方法二

  1. Height rule: if the height of tree i is less than that of tree j, then make j the parent of i; otherwise,make i the parent of j.(如果树 i 的高度小于树 j 的高度,则使 j 成为 i 的父;否则,使 i 成为 j 的父节点)
  2. 总而言之:高度低的树挂到高度高的树的下面
  3. When processing a equivalence pair, we need to operate Find twice, WeightUnion once. Example of improvement:(在处理等价对的时候,我们需要Find操作两次,WeightUnion一次。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//c++
//存在疑问?
int Find(int e) {
/* C++ */
int j = e;
while(!root[j])
j=parent[j];
int f = e;
while(f!=j) {
int pf = parent[f];
parent[f] = j;
f = pf;
}
}
1
2
3
4
5
6
7
//java是用来记录树高
public int find(int x) {
if( s[x] < 0 )
return x;
else
return s[x] = find(s[x]);
}

3.4. 性能增强

  1. improve Union in order to decrease the time each find take, so that the height of tree will not increase linearly.(改进并查集以减少每次查找所需的时间,从而使树的高度不会线性增加)
  2. Improvement of Find –path compression(查找路径压缩的改进)

2019-数据结构-数据结构7.1-The Disjoint Set
https://spricoder.github.io/2020/01/17/2019-Data-Structure/2019-Data-Structure-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%847.1-The%20Disjoint%20Set/
作者
SpriCoder
发布于
2020年1月17日
许可协议