2.3.weighted-quick-union——加权quick-union算法

上篇的quick-union算法的效率之所以低(平方级别),最主要的原因是union(p,q)方法,随意将一棵树连接到另一棵树上(一棵树对应一个连通分量)。

1.如果是小树(高度低)连接到大树的根节点(高度高),则小树的高度加1,而整个树的高度不变。

2.如果是大树(高度高)连接到小树的根节点(高度低),则大树的高度加1,而整个树的高度由原来的小树高度变成大树高度加1。

union-find算法分析(2)_第1张图片

根据quick-union算法分析,find(p)访问的数组的次数与节点p所在的高度有关。高度越高,访问数组的次数越多。

由此可见,为了减少访问数组的次数,提高算法效率,在执行union(p,q)操作时,确保是情况1,即小树连接到大树上。为此,需要一个数组sz[]来记录触点p所在的连通分量含有的触点(触点越多,对应的树的节点越多,即为大树)。

3.当然还有一种特殊情况,即p和q所在的连通分量对应的树高度相等,此时无论是p连接到q还是q连接到p,树的高度都会增加。在加权quick-union算法中,这是最坏的情况。

因此,由加权quick-union构成的树的高度将远小于未加权所构造的树的高度。

   1: public class WeightedQuickUnionUF extends QuickUnionUF {
   2:  
   3:     /**
   4:      * sz[p]表示触点p所在的连通分量所含的触点数
   5:      */
   6:     private int[] sz;
   7:  
   8:     public WeightedQuickUnionUF(int N) {
   9:         super(N);
  10:         // TODO Auto-generated constructor stub
  11:         sz = new int[N];
  12:         for (int i = 0; i < N; i++)
  13:             sz[i] = 1;// 初始化时,每个触点都是一个连通分量
  14:     }
  15:  
  16:  
  17:     @Override
  18:     public void union(int p, int q) {
  19:         // TODO Auto-generated method stub
  20:         int pRoot = find(p);
  21:         int qRoot = find(q);
  22:         
  23:         if(pRoot == qRoot)
  24:             return;
  25:         
  26:         if(sz[pRoot] < sz[qRoot]){//当触点p所在的连通分量对应的树是小树,则连接到q的连通分量上去
  27:             id[pRoot] = qRoot;
  28:             sz[qRoot] += sz[pRoot];//不要忘了,被连接的树包含的节点数要相应的增加
  29:         }else{//当触点p所在的连通分量对应的树是大树,则q所在的连通分量连接到p的连通分量上去
  30:             id[qRoot] = pRoot;
  31:             sz[pRoot] += sz[qRoot];
  32:         }
  33:         
  34:         count -- ;
  35:     }
  36:     
  37:     public static void main(String[] args) {
  38:         DirectInput.directInput(args);
  39:         int N = StdIn.readInt();
  40:         UF uf = new WeightedQuickUnionUF(N);
  41:         for(int i=0;i 
     
  42:             int p = StdIn.readInt();
  43:             int q = StdIn.readInt();
  44:             
  45:             if(uf.connected(p, q)) continue;
  46:             
  47:             uf.union(p, q);
  48:             StdOut.println(p + " " + q);
  49:         }
  50:         StdOut.println(uf.count() + " components");
  51:     }
  52:  
  53: }

 

测试结果

union-find算法分析(2)_第2张图片

算法分析

1.最坏的情况:将要被归并的树的大小总是相等的(且总是2^n)。每个树均是含有2^n节点的满二叉树,因此高度正好是n。当归并两个含有2^n节点的树时,得到含有2^(n+1)个节点的书,由此树的高度增加到n+1。由此可知,加权quick-union算法是对数级别的。即对于N个触点所构成的树最高的高度为lgN。

2.情况1的最坏的情况是怎么得来的?

简单的分析可知,加权quick-union算法不可能会得到线性表(未加权的quick-union会产生退化成线性表的树)。因此,每层的节点越多,树的高度就越小。最坏的情况就是满二叉树。

3.加权quick-union算法处理N个触点,M条连接时最多访问数组cMlgN次。(c为常数。每处理一条连接,调用一次union(p,q)方法。而union(p,q)是lgN级别的。lg[height(p)] + lg[height(q)] + 5 = clgN,M次即为cMlgN)。而quick-find算法以及某些情况下未加权的quick-union算法至少访问数组MN次。

 union-find算法分析(2)_第3张图片