# Lecture07.1-DisjointSEet
- 有很多时候是不考的
- 一般就是图里的最小生成树算法会考到
- 但是两个核心操作 find union 要清晰
- 考的概率很低
- 性能提升考吗?
# The Disjoint Set ADT (不相交集,并查集)
- 使用来表示离散中的等价类和等价关系的表示。
# 1. 等价类 (Equivalence Class)
- 等价类的定义: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 才是一个等价类)
- Reflexive x ≡ x.(自反性)
- Symmetric x ≡ y,y ≡ x(对称性)
- Transitive x ≡ y and y ≡ z,then x ≡ z(传递性)
- Eg.
# 2. 并查集提供的功能
- Combine(a,b):combine the equivalence classes that contains elements a and b into a single class(Combine(a,b): 合并包含元素 a 和 b 的两个等价类为一个等价类)
- Find(e):determine the class that currently contains element e.(Find(e): 找到包含元素 e 的等价类)
# 2.1. Combine (a,b) 合并
- Combine(a,b) is equivalent to i=Find(a); j=Find(b); if(i!=j) Union(i,j);
# 3. 并查集的物理实现
- 并查集的物理实现是通过森林来表示。
- parent 数组中存储的值为 0 的时候,这个结点表示为根结点
- 所以这个更快速的支持从下向上查询
树的结构
//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 的实现
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 )// 这里是 - 1 表示根节点 | |
return x; | |
else | |
return find( s[x] ); | |
} |
# 3.2. 性能估计
- Time complexity:(算法复杂度)
- Find-- O(h), h 是指树高
- Union-- θ(1)
- 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. 方法一
- 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,反之亦然)
- 结点数少的树挂到结点多的树下面
# 3.3.2. 高度问题的实现
- 为了实现我们新建一个 bool 类型数组来记录是否是根节点。
- 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.(除了父字段外,每个节点都有一个布尔字段根。如果当前节点是根节点,则根字段为真。每个根节点的父字段用于统计树中的节点总数。)
- 也就是单独使用了一个布尔数组来实现是否为根。
//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; | |
} | |
} |
- 如何省略去标记根的数组?
- 使用负数来记录树高
//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; | |
} | |
}// 注意到负数会都反过来 |
- 例子如下
# 3.3.3. 方法二
- 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 的父节点)
- 总而言之: 高度低的树挂到高度高的树的下面
- When processing a equivalence pair, we need to operate Find twice, WeightUnion once. Example of improvement:(在处理等价对的时候,我们需要 Find 操作两次,WeightUnion 一次。)
//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; | |
} | |
} | |
//java 是用来记录树高 public int find (int x) { if ( s [x] < 0 ) return x; else return s [x] = find (s [x]);} |
# 3.4. 性能增强
- improve Union in order to decrease the time each find take, so that the height of tree will not increase linearly.(改进并查集以减少每次查找所需的时间,从而使树的高度不会线性增加)
- Improvement of Find –path compression (查找路径压缩的改进)