详细解析参照算法(第4版)1.5章——案例研究:union—find算法
1.union-find法的API
public class UF UF(int N) 以整数标识(0—N-1)初始化N个标识 void union(int p,int q) 在触点p和q之间添加一条连接 void find(int p) p所在连通分量的标识符(0—N-1) void connected(int p,int q) 判断触点p和q是否连通,即p和q是否在同一连通分量 int count() 连通分量的数目
2.union-find的实现
1: public abstract class UF {2: protected int count;3: protected int[] id;4:
5: public UF(int N) {6: count = N;
27: if (find(i) == pID)7: id = new int[N];8: for (int i = 0; i < N; i++)9: id[i] = i;
10: }
11:
12: public abstract int find(int p);13:
14: public abstract void union(int p,int q);15:
16: public boolean connected(int p,int q){17: return find(p) == find(q);18: }
19:
20: public int count(){21: return count;22: }
23: }
2.1.quick-find算法
1: public class QuickFindUF extends UF {2:
3: public QuickFindUF(int N) {4: super(N);5: }
6:
7: @Override
8: public int find(int p) {9: // TODO Auto-generated method stub10: // 触点p为索引,id[p]即是p所在的连通分量的标识符11: return id[p];12: }
13:
14: /**15: * 如果p和q在同一个连通分量,则p和q连通 否则,要将p和q连通(两个连通分量合并),即将p的连通分量所有触点的连通分量改成q的连通分量16: */17: @Override
18: public void union(int p, int q) {19: // TODO Auto-generated method stub20: int pID = find(p);21: int qID = find(q);22:
23: if (pID == qID)24: return;25:
26: for (int i = 0; i < id.length; i++)27: if (find(i) == pID)28: id[i] = qID;
29: count--;
30: }
31:
32:
33:
34: }
算法分析
1.union(p,q)会访问数组次数N+3~2N+1
分析:(1)两次find()操作,访问2次数组
(2)扫描整个数组id[],判断p和q是否在同一个连通f分量if(find(i)==pID),访问N次数组
(3)①只有p,其余触点均不和p在同一连通分量 id[p] =qID,访问1次数组
②除了q本身,其余均和p在同一连通分量 id[i] = qID(i≠q),访问 N-1次数组,故总的访问次数①2+N+1 = N+3 ②2+N+N-1 = 2N+1
2.在最好的情况下(union(p,q)访问数组N+3次),N个整数要进行N-1次合并union(p,q)操作,访问数组(N+3)(N-1)~N^2。
quick-union算法是平方级别的。
测试结果
1: public static void main(String[] args) {2: DirectInput.directInput(args);
3: int N = StdIn.readInt();4: UF uf = new QuickFindUF(N);5: while(!StdIn.isEmpty()){6: int p = StdIn.readInt();7: int q = StdIn.readInt();8: if(uf.connected(p, q) ) continue;9: uf.union(p, q);
10: StdOut.println(p+ " " + q);11: }
12:
13: StdOut.println(uf.count() + " components");14: }
2.2.quick-union算法
1: /**2: * 以触点p为索引的数组id[p]是p所在的连通分量中的另一个触点q<br>3: * 即p与q是连通的4: *5: * @author YoungCold6: *7: */8: public class QuickUnionUF extends UF {9:
10: public QuickUnionUF(int N) {11: super(N);12: }
13:
14: /**15: * 找到p的根触点<br>16: * 根触点:符合id[p]=p,即指向自己的触点,即为根触点17: */18: @Override
19: public int find(int p) {20: // TODO Auto-generated method stub21: while (p != id[p])22: p = id[p];
23: return p;24: }
25:
26: /**27: * 当p的根触点和q的根触点不同时,说明p和q不在同一个连通分量<br>28: * 要想p和q连通,即将p(q)的根触点(id值为本身)指向q(p)的根触点29: */30: @Override
31: public void union(int p, int q) {32: // TODO Auto-generated method stub33: int pRoot = find(p);34: int qRoot = find(q);35:
36: if (pRoot == qRoot)37: return;38:
39: // 将p的根触点(id值为本身)指向q的根触点40: id[pRoot] = qRoot;
41: //每次合并,连通分量的数目减一42: count--;
43: }
44:
45: public static void main(String[] args) {46: DirectInput.directInput(args);
47: int N = StdIn.readInt();48: UF uf = new QuickUnionUF(N);49: for (int i = 0; i < N; i++) {50: int p = StdIn.readInt();51: int q = StdIn.readInt();52: if (uf.connected(p, q))53: continue;54:
55: uf.union(p, q);
56: StdOut.println(p + " " + q);57: }
58: StdOut.println(uf.count() + " components");59: }
60:
61: }
(1)与quick-find不同的是,以触点p为索引的数组id[]不再表示p所在的连通分量,而是表示p所在的连通分量的另一个触点(也可能是它本身),当它是本身时,该触点就是根触点,也就是连通分量所对应的树的根节点,这种联系称之为链接。
(2)森林的表示,实际上id[]数组用父链接的形式表示了一片森林。无论从任何触点所对应的节点开始跟踪链接,最终都能到达含有该节点的树的根节点(可用数学归纳法证明)。
算法分析
1.quick-union算法看似比quick-find算法块,因为它不需要为每对输入遍历整个数组。
2.①最好的情况下find(p),仅访问一次数组,此时触点p为根触点。
②最坏的情况下find(p),访问数组2N-1次
while(p != id[p]) p = id[p];
最坏的情况是触点p所在的连通分量对应的树退化成线性表而且仅有一个连通分量,而p在线性表的表尾。
while()循环的判断条件要访问N次数组,while()循环的执行体要访问N-1 次数组(当最后一次到达根节点时,不执行循环体)。共2N-1次。
3.由此可见,find(p)访问数组的次数,是由触点p对应的节点在树的高度所决定的。设p在树的中的高度为h,则访问数组的次数为2h+1次。
4.假设输入的是有序整数对0-1、0-2、0-3…0-N,N-1对之后的N个触点将全 部处于同一个连通分量内(详见main()),且由quick-union算法得到的树的高度为N-1,其中0→1,1→2…N-1→N。
对于整数对0-i,执行union(0,i),将访问2i+1次数组。
①其中0的根触点是i-1,高度是i-1,根据3,find(0)访问数组2i-1次
②其中i的根触点是i,高度是0,根据3,find(i)访问数组1次
③将i-1的根触点(原指向本身,现指向触点i)的数组内容变成i,访问数组1次
PS:书上是2i+2次,我分析是0-i是连通的,这样0的根触点是i,i的根触点是i,find(0)访问2i+1次,find(i)访问1次
共2i+2次。
可根据main()方法,此时0和i应该不连通才对。
5.处理N对整数所需的所有find()操作访问是;Σ(1→N)(2i) = 2(1+2+…N) ~N2
可以看出quick-union和quick-find都是平方级别的算法。
转载于:https://blog.51cto.com/youngcold/1106992