並查集算法:quick-find、quick-union、優化的 quick-union

1. 問題描述

可以想象一張地圖上有很多點,有些點之間是有道路相互聯通的,而有些點則沒有。如果我們現在要從點 A 走向點 B,那麼一個關鍵的問題就是判斷我們能否從 A 走到 B 呢?換句話說,A 和 B 是否是連通的。這是動態連通性最基本的訴求。現在給出一組數據,其中每個元素都是一對“點”,代表這對點之間是聯通的,我們需要設計一個算法,讓計算機依次讀取這些數據,最後判斷出其中任意兩點是否連通。

2. quick-find

保證當且僅當 id[p] 等於 id[q] 時 p 和 q 是連通的。即在同一個分量中的所有觸點在 id[ ] 中的值必須全部相同。

import java.util.Scanner;

public class UF {
    private int[] id;//分量 id(以觸點爲索引)
    private int count;//分量數量

    public UF(int N){
        //初始化分量 id 數組
        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){
        return id[p];
    }

    public void union(int p,int q){
        //將q和p歸併到相同的分量中
        int pID = find(p);
        int qID = find(q);

        //如果p和q已經在相同的分量之中則不需要採取任何行動
        if(pID == qID) return;

        //將p的分量重命名爲q的名稱
        for(int i = 0;i < id.length;i++){
            if(id[i] == pID) id[i] = qID;
        }
        count--;
    }

    //解決動態連通性問題
    /*測試數據:
    10
    4 3
    3 8
    6 5
    9 4
    2 1
    5 0
    7 2
    6 1
    1 0
    6 7
    輸出: 2 components */
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        UF uf = new UF(N);
        for(int i = 0;i < N;i++){
            int p = in.nextInt();
            int q = in.nextInt();
            if(uf.connected(p,q)) continue;//已經連通則忽略
            uf.union(p,q);//歸併分量
        }
        System.out.println(uf.count + " components");
    }
}

時間複雜度:find() 爲 O(1),union() 爲 O(N)。

3. quick-union

每個觸點所對應的 id[ ] 元素都是同一個分量中的另一個觸點的名稱(也可能是他自己)。在實現 find() 方法時,從給定觸點開始,由它鏈接得到另一個觸點,再由這個觸點的鏈接到達第三個觸點,如此繼續直到到達一個根觸點,即鏈接指向自己的觸點。

package fundamentals;

import java.util.Scanner;

public class UF {
    private int[] id;//分量 id(以觸點爲索引)
    private int count;//分量數量

    public UF(int N){
        //初始化分量 id 數組
        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);
    }

    private int find(int p){
        //找出分量的名稱
        while(p != id[p]) p = id[p];
        return p;
    }

    private void union(int p,int q){
        //將p和q的根節點統一
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot) return;
        id[pRoot] = qRoot;

        count--;
    }

    //解決動態連通性問題
    /*測試數據:
    10
    4 3
    3 8
    6 5
    9 4
    2 1
    5 0
    7 2
    6 1
    1 0
    6 7
    輸出: 2 components */
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        UF uf = new UF(N);
        for(int i = 0;i < N;i++){
            int p = in.nextInt();
            int q = in.nextInt();
            if(uf.connected(p,q)) continue;//已經連通則忽略
            uf.union(p,q);//歸併分量
        }
        System.out.println(uf.count + " components");
    }
}

時間複雜度取決於形成的樹的高度。
在這裏插入圖片描述

4. 優化的 quick-union

與其在 union() 中隨意將一棵樹連接到另一顆樹,現在記錄每一顆樹的大小並總是將較小的樹連接到較大的樹上。
在這裏插入圖片描述

import java.util.Scanner;

public class WeightedQuickUnionUF {
    private int[] id;//分量 id(以觸點爲索引)
    private int[] sz;//各個根節點所對應的分量的大小
    private int count;//分量數量

    public WeightedQuickUnionUF(int N){
        //初始化分量 id 數組
        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;
    }

    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;
    }

    private void union(int p,int q){
        //將p和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--;
    }

    //解決動態連通性問題
    /*測試數據:
    10
    4 3
    3 8
    6 5
    9 4
    2 1
    5 0
    7 2
    6 1
    1 0
    6 7
    輸出: 2 components */
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        WeightedQuickUnionUF uf = new WeightedQuickUnionUF(N);
        for(int i = 0;i < N;i++){
            int p = in.nextInt();
            int q = in.nextInt();
            if(uf.connected(p,q)) continue;//已經連通則忽略
            uf.union(p,q);//歸併分量
        }
        System.out.println(uf.count + " components");
    }
}

時間複雜度:find() 和 union() 都爲 O(logN)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章