本文主要学习了《算法》(Robert Sedgewick Kevin Wayne)中关于union-find的介绍,并对所有的重要知识点进行总结。
1、定义
p、q代表单个触点,整数对(p,q)代表这两个值代表的点相连。每次输入一对(p,q)时判断两个点是否连通,如果不连通则添加连接。
2、应用
1) 大型网络中,已知一些计算机相连,判断任意两个点是否连通
2) 变量名等价性:给定两个变量,这两个变量指向同一个对象的引用,则称为等价。判断任意两个变量是否等价
3、抽象
在处理(p,q)时判断是否属于同一个集合:如果是,说明已经连通;如果不是,说明p、q未连通,则添加连接并且合并集合
4、算法变量及函数
变量:count表示分量数量;id数组表示分量所在集合id
1) int count():返回连通分量的个数,有合并操作时个数减一
2) boolean connected(p,q):返回p、q是否连通
3) int find(p):返回p所在分量标识符
4) void union(p,q):在p、q之间添加连接
5、算法实现
1) quick-find:id中的值即为分量的id,在同一个集合中的触点对应的id都相同,这样保证connected只需要判断两个触点的find返回值是否相同即可。而union函数需要保证合并集合时将id也对应修改为相同。
int find(int p){
return id[p];
}
void union(int p, int q){
int pID = find(p);
int qID = find(q);
if(pID == qID)
return;
for(int i=0; i<id.length; i++){
if (id[i] == pID)
id[i] = qID;
}
count--;
}
但这样的方法union中计算量很大,需要遍历整个id数组来改变索引
2) quick-union:为了能够加快union的速度,我们只需要保证union能够使的两个需要连通的节点的根节点相同即可;而这样的话,找到分量的名称,需要找到根节点对应的分量,所以find函数需要通过循环一直找到分量。
int find(int p){
while(p != id[p])
p = id[p];
return p;
}
void union(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if(pRoot == qRoot)
return;
id[pRoot] = qRoot;
count--;
}
上面的实现find函数中,知道找到触点在id中的分量和其本身相同时,才算作找到其根节点分量。
但是可能遇到情况:连通图是线性的,构造的树越来越深,每次find中的循环操作时间越来越长,从而使得算法效率较低。
3) 加权quick-union:这是对上一个方法的小改动,从而避免数越来越深的情况。改动就是在union的根节点改变时,将小的树挂到大的树上。所以这里需要引入另外一个变量sz数据,用来表示每一个集合的大小,初始时也是每一个初始化为1。
int find(int p){
while(p != id[p])
p = id[p];
return p;
}
void union(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if(pRoot== qRoot)
return;
if(sz[pRoot]<sz[qRoot]){
id[pRoot]=qRoot;
sz[qRoot] += sz[pRoot];
} else{
id[qRoot]=pRoot;
sz[pRoot] += sz[qRoot];
}
count--;
}
3) 最优算法:我们想要保证能够在常数时间内完成find和union操作,但这样的目标是难以实现的。所以算法的进一步改进只能集中在扁平化生成的树,所以改动的地方在于:在find中添加循环,使得在路径上遇到的所有节点,都将其直接链接到根节点。这是目前为止最为优化的算法。