生成树机制实验(1)并查集1
定义
并查集(Union Find)是用于管理分组的数据结构。具备两个操作:(1)查询元素a 和 b 是否为同一组
(2) 将元素 a 和 b 合并为同一组。
并查集不能将在同一组的元素拆分为两组。
结构
利用树来实现并查集
有 n 个结点,我们用 n 个元素来初始化这 n 个节点(1 ~ n)。现在分别用树形结构和非树形结构来表示,以 n = 6 为例
1 5
/ | \ |
2 3 4 6
非树形结构: (1,2,3,4) (5,6)
使用树形结构来表示,每一个组都对应一棵树,然后将问题转化为树的问题,看两个元素是否为同一组,只需要看两个元素的根是否一致。合并时,只需要将一组的根和另一组的根相连即可。
实现
int node[n]; // 定义结点
// 结点初始化
void Init(int n){
for(int i = 0; i < n; i++){
node[i] = i;
/* 初始时,每个结点的根结点为自己*/
}
}
// 查找当前元素所在树的根结点
int find(int x){
if(x == node[x])
return x;
return find(node[x]);
}
/*这里定义一组递归,例如上面的例子,我们寻找 4, node[4] == 1 !=4 ,所以需要 return find(node[4]),就是顺着根往上找,return find(1),即为1 */
// 合并元素 x,y 所处的集合
void Unite(int x,int y){
x = find(x);
y = find(y);
if(x == y)
return ;
node[x] = y;
/* node[x]=y, 表明将 x 的根结点赋值为 y*/
}
// 判断x,y属于同一集合
bool same(int x, int y){
return find(x) == find(y);
}
优化并查集
如果随意合并子树会导致树的退化。在树形结构中,如果发生退化,复杂度将会变高(直接退化为线性结构)等等,解决措施:
- [高度比较] 对于每一棵树,记录树的高度(rank)。在每次合并操作时,将高度最小的树放在高度高的树下,成为它的子树
- [路径压缩] 将原先间接与根相连的结点,直接相连(在每次查询时完成这一操作)
1 1
| / | \
2 2 3 4
|
3
|
4
优化后的算法
int node[n]; // 定义结点
int rank[n]; // 定义高度
// 初始化 n 个结点
void Init(int n){
for(int i = 0; i < n; i++){
node[i] = i;
rank[i] = 0;
}
}
// 查找当前元素所在树的根结点(等价类的代表元)
int find(int x){
if(x == node[x])
return x;
return node[x] = find(node[x]);
/* 这里比 return find(node[x])多做的一步是: 将结点直接连到根结点上
node[x] = find(node[x])
假设以1-2-3-4为例子
1-2-3-4
1-1-2-3
所以 find(4) node[4] == 3 !=4 return node[4]= find(node[4])= find(3)
find(3) node[3]==2 !=3 return node[3] = find(node[3])=find(2)
find(2) node[2]==1 !=2 return node[2] = find(node[2]) = find(1) = 1
所以结果为: node[2] = 1,node[3]=1,node[4] = 1
省去后续的遍历*/
}
// 合并元素 x,y 所处的集合
void Unite(int x,int y){
// 查找到 x,y 的根结点
x = find(x);
y = find(y);
/*这一系列find操作中,可以理解为把 x 所在的边对应的一连串向上的结点连到根结点,和x 不同分支的没有合并,因此高度可能不同*/
if(x == y)
return;
/* 判断两棵树的高度,然后决定谁为子树
三种情况,因为如果 rank[x] > rank[y] 或者 rank[y] > rank[x],
添加子树的高度为 rank[y] + 1 <= rank[x],
只有当两者高度相等时,完成 rank[x]++,x 作为根结点的操作*/
if(rank[x] < rank[y]){
node[x] = y;
}
else{
node[y] = x;
if(rank[x] == rank[y]) rank[x]++;
}
}