美文网首页
Union-Find算法

Union-Find算法

作者: sleepyjoker | 来源:发表于2016-09-11 15:12 被阅读745次

    动态连通性问题中,如何判断触点是否相连接,可以抽象表述为如何编写程序来过滤掉序列中所有无意义的整数对。
    连通性问题只要求我们的程序能够判定给定的整数对p,q是否相连接,但并不要求给出两者之间通路上的所有连接,反而加大了问题的难度。为了简化说明问题,我们不妨自己设计一份API来封装所需要的操作:初始化,连接两个触点,判断包含某两个触点的分量,判断两个触点是否在同一个分量,返回所有分量的数量。
    通过自己定义如何连接,我们可以很容易的判断是否连接。

    public class UF{
        private int[] id;
        private int count;
        public UF(int N){
            count = N;
            id = new int[N];
            for(int i = 0; i < N; i++)
                id[i] = i;
        }
        public int count(){
            return count;
        }
        public boolean connected(int p, int q){
            return find(p) == find(q);
        }
        public int find(int p)
        public int union(int p, int  q)
        public static void main(String[] args){
            int N = StdIn.readInt();
            UF uf = new UF(N);
            while(!StdIn.isEmpty()){
                int p = StdIn.readInt();
                int q = StdIn.readInt();
                if(uf.connected(p, q)) continue;
                uf.union(p, q);
                StdOut.println(p + " " + q);
            }
        }
    }
    

    上述代码实现了基本的API,还剩下union和find两个函数没有实现。下面来看几种不同的解决方案。

    • quick-find
    private int find(int p){
        return id[p];
    }
    public void union(int p, int q){
        int pID = find(p);
        int qID = find(q);
        if(pID == qID)  return;
        for( int i=0; i<id; i++)
            if( id[i] == pID) id[i] = qID;
        count--;
    }
    

    quick-find算法中,find()操作只需要访问数组一次,但是每一次归并两个分量的union()操作需要访问数组(N+3)(2N+1)次。假使我们使用quick-find算法解决连通性问题并最终只剩下一个分量,至少需要调用N-1次的union(),即至少(N+3)(N-1)N²次数组访问。

    • quick-union
    private int find(int p){
        while( p != id[p])  p=id[p];
        return p;
    }
    public void union(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);
        if( pRoot == qRoot)   return;
        
        id[pRoot] = qRoot;
        count--;
    }
    

    quick-union算法将会把触点连接成为一棵树,我们定义一棵树的大小为节点的数量,树中一个节点的深度是它到根节点路径上的链接数,树的高度是它最大的深度。
    那么quick-union算法中的find()方法访问数组的次数为1加上给定触点所对应节点深度的两倍。union()方法访问数组的次数为两次find()操作(如果union()中两个触点位于不同的分量中还要再加1)。

    • weighted-quick-union
      我们很容易发现quick-union算法在最坏的情况下,仍然是平方级别的,此时形成的树没有分岔。我们可以简单的修改从而避免这一情况。quick-union中随意将一棵树连接至另一棵树,现在我们记录每一颗树的大小以确保将小树连接在大树上。
    public class weightedQuickUnionUF{
        private int[] id;
        private int[] sz;
        private int count;
        public WeightedQuickUnionUF(int N){
            count = N;
            id = new int[N];
            for(int i=0; i<N; i++)  id[i] = i;
            sz = new int[N];
            for(int i=0; i<N; i++)   id[i] = 1;
        }
        public int count(){
            return count;
        }
        public boolean connected(int p, int q){
            return find(p) == find(q);
        }
        private int find(int p){
            while( p != id[p]) p = id[p];
            return p;
        }
        public void union(int p, int q){
            int i = find(p);
            int j = find(q);
            if( i == j)  return;
            if( sz[i] < sz[j]) { id[i] = j; sz[j] += sz[i];}
            else   { id[j] = i; sz[i] += sz[j]; }
            count--;
        }
    }

    相关文章

      网友评论

          本文标题:Union-Find算法

          本文链接:https://www.haomeiwen.com/subject/hhuuettx.html