天天看點

并查集Java實作

定義

就是有“合并集合”和“查找集合中的元素”兩種操作的關于資料結構的一種算法。

  • 連接配接兩個對象
  • 判斷是否這兩個對象是連接配接的
并查集Java實作

上例中,0,7是沒有路徑的;8,9之間有一條可達路徑,是以是連接配接的。

模組化

關于連接配接

  • 等價性: p連接配接到p,每個對象都能連接配接到自己
  • 對稱性: p連接配接到q;等價于q連接配接到p
  • 傳遞性: 如果p連接配接到q,q連接配接到r,那麼,p連接配接到r。

快速查找

資料結構

這裡用到的資料結構是數組。

  • 對象索引整數數組,用索引來表示N個對象,0表示第一個對象,以此類推。
  • 如果p和q是連接配接的,那麼它們數組的值相同。
并查集Java實作

查找

隻需要檢查p和q是否有相同的值。

合并

将它們的值設為相同的。假如需要合并兩個分别包含p和q的子集,将所有值等于id​​p​​的對象的值改為id[q],即改為q的值。

實作

初始化、判斷連接配接以及合并

public class QuickFindUF
    private int [] id;
    public QuickFindUF(int N){
        id = new int[N];
        for (int i = 0; i < N; i++) {
            id[i] = i;  //初始化
        }
    }

    /**
     * 判斷p和q是否是連接配接的
     * @param p
     * @param q
     * @return
    public boolean connected(int p,int q){
        return id[p] == id[q];
    }

    /**
     * 合并p的子集和q的子集
     * 将所有值等于id[p]的對象的值改為id[q]
     * @param p
     * @param
    public void union(int p,int q){
        int pid = id[p];
        int qid = id[q];
        for (int i = 0; i < id.length; i++) {
            if(id[i] == pid){
                id[i] = qid;
            }
        }
    }

}      

時間複雜度分析

算法 初始化 合并 查詢
quick-find N N 1

在這個實作中,合并操作的代價太高了。如果需要在N個對象上進行N次合并,那就是O(n^2)。效率不高。

快速合并

資料結構

也是數組。

  • 對象索引整數數組,用索引來表示N個對象,0表示第一個對象,以此類推。
  • id[i]存放的值代表i的父節點索引
  • 若id[i]==i,則說明i是根節點
并查集Java實作

上面3的父節點是4,4的父節點是9,9的父節點也是9,9為根節點。

查找

檢查p和q根節點是否相同。

合并

合并操作就非常簡單了。

要合并p,q兩個對象的分量(合并樹),将p的根節點設為q的根節點(p=>q p的根節點指向q)。

并查集Java實作
并查集Java實作

上面隻需要将9的值改為6即可。

實作

public class QuickUnionUF
    private int [] id;

    public QuickUnionUF(int N){
        id = new int[N];
        for (int i = 0; i < N; i++) {
            id[i] = i;
        }
    }

    /**
     * 找到i的根節點
     * @param i
     * @return
    private int root(int i){
        while(i != id[i]){
            i = id[i];//i和id[i] 一起更新
        }
        return i;
    }

    /**
     * 判斷p和q是否是連接配接的
     * @param p
     * @param q
     * @return
    public boolean connected(int p,int q){
        return root(p) == root(q);
    }

    /**
     * 合并p和q
     * 将p的根節點設為q的根節點(p=>q p的根節點指向q)。
     * @param p
     * @param
    public void union(int p,int q){
        int qroot = root(q);//找到q的根節點
        int      

時間複雜度分析

算法 初始化 合并 查詢
quick-find N N 1
quick-union N N N

最壞的情況下,會生成一顆瘦長樹。

權重快速合并(Weighted quick-union)

資料結構

與快速合并類似,但是維護了一個額外的數組sz[i],它包含了以i為根節點的樹的節點數量。

查找

與快速合并完全相同

合并

  • 将小樹指向大樹的根節點
  • 更新sz[]數組

實作

package com.algorithms.UnionFind;

public class WeightedQuickUnionUF
    private int [] id;
    private int [] sz;//size of component for roots
    private int count;//number of components

    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++) sz[i] = 1;
    }

    public int count(){
        return count;
    }

    /**
     * 找到i的根節點
     * @param i
     * @return
    private int root(int i){
        while(i != id[i]){
            i = id[i];//i和id[i] 一起更新
        }
        return i;
    }

    /**
     * 判斷p和q是否是連接配接的
     * @param p
     * @param q
     * @return
    public boolean connected(int p,int q){
        return root(p) == root(q);
    }

    /**
     * 合并p和q
     * 将p的根節點設為q的根節點(p=>q p的根節點指向q)。
     * @param p
     * @param
    public void union(int p,int q){
        int i = root(q);
        int j = root(p);
        if( i == j) return;

        //Make smaller root point to larger one.
        if(sz[i] <sz[j]){
            id[i] = j;
            sz[j] += sz[i];
        }else{
            id[j] = i;
            sz[i] += sz[j];
        }
        count --;
    }
}      
算法 初始化 合并 查詢
quick-find N N 1
quick-union N N+ N
Weighted quick-union N lgN+ lgN

​+​

​​ 加上查找根節點的消耗

​​

​*​

​ 樹中任意節點的深度,最大是lgN (以2為底)

但是,還能再改進!

路徑壓縮

在計算p節點的根後,将每個經過的節點都指向根。

并查集Java實作

上圖中,從p找到根0,需要經過9(當然要包括自己),6,3,1(已經指向根節點了)這些節點。讓它們都指向根:

并查集Java實作

如果要實作完全展平,會很複雜。我們可以選擇一種折中的方案,将節點p指向節點p的祖父(父節點的父節點)節點。

神奇的是,代碼隻需要在權重快速合并的基礎上,進行一些小的改動:

将路徑上的每個節點指向它的祖父節點:

private int root(int i){
        while(i != id[i]){
            id[i] = id[id[i]];//(父節點的父節點)
            i = id[i];//i和id[i] 一起更新
        }
        return      

繼續閱讀