1. 程式人生 > >小橙書閱讀指南(十三)——連通性算法(union-find)

小橙書閱讀指南(十三)——連通性算法(union-find)

總數 速查 比較 on() 分享圖片 ace 效率 說明 ++

上一章我大概說明了什麽是圖論以及無向圖的基礎概念,本章我們要研究一種更普遍的算法——連通性算法。它屬於圖論的分支,也是一種抽象算法。在深入算法之前,我們先提出一個具體的問題:假設在空間中存在N個點,我們可以通過線段連接任意兩點,相互連接的點屬於同一組連通分量,我們如何計算點p和點q之間是否連通。算法的核心是:如何表示連通性以及如何檢查連通性。

下面提供算法的抽象接口:

/**
 * 連通性算法
 */
public interface UnionFind {
    /**
     * p點和q點之間添加一條通路
     *
     * @param p
     * @param q
     
*/ void union(int p, int q); /** * 獲取p點的連通分量 * * @param p * @return */ int find(int p); /** * 判斷p點和q點是否存在一條通路 * * @param p * @param q * @return */ boolean connected(int p, int q); /** * 連通分量的數量 * * @return */
int count(); }

一、快速查詢算法Quick-Find

我們將空間中的點這一概念抽象成int[](整形數組),i代表不同的點int[i]代表不同的連通分量。一種比較容易理解的想法是,從屬於同一組連通分量的任一點p和q必定int[p]等於int[q]。因此當我們需要查詢點p和q是否連通的時候只需要判斷int[p] == int[q]是否成立即可。

/**
 * 連通性算法:quick-find
 */
public class QuickFind implements UnionFind {
    private int[] id; // 分量id
    private int
count; public QuickFind(int n) { count = n; id = new int[n]; for (int i = 0; i < n; i++) { id[i] = i; } } @Override 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.length; i++) { if(id[i] == pID) { id[i] = qID; } } count--; } @Override public int find(int p) { return id[p]; } @Override public boolean connected(int p, int q) { return find(p) == find(q); } @Override public int count() { return count; } }

find()操作的速度顯然是很快的,因為它只需要訪問id[]數組一次。但是對於每一對數組union()都需要掃描整個id[]數組。因此quick-find算法一般無法處理大型數組。

算法圖示:

技術分享圖片

二、快速連接算法Quick-Union

我們要討論的下一個算法的重點是提高union()方法的速度,為此可能會稍微犧牲一下find()的效率,但是通常情況下這樣做是值得的。Quick-Union算法考慮把屬於同一組連通分量的點連接成一棵樹,i代表點,int[i]代表i的父節點,根節點p等於int[p]。

public class QuickUnion implements UnionFind {
    private int count;
    private int[] id;

    public QuickUnion(int n) {
        count = n;
        id = new int[n];
        for(int i = 0; i < n; i++) {
            id[i] = i;
        }
    }
    @Override
    public void union(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot) {
            return;
        }

        id[pRoot] = qRoot;
        count--;
    }

    @Override
    public int find(int p) {
        while(p != id[p]) {
            p = id[p];
        }
        return p;
    }

    @Override
    public boolean connected(int p, int q) {
        return false;
    }

    @Override
    public int count() {
        return 0;
    }
}

快速連接算法的每一次連接會分別遍歷兩次連通分量,在連通分量中包含元素數量相對總數而言比較小的情況下可以提供非常不錯的速度。

算法圖示:

技術分享圖片

事實上,無論是Quick-Find算法還是Quick-Union算法,他們在圖論的基礎上基本是起到相互補充的作用。更重要的一點是,我們通過對他們的學習可以認識到,十全十美的算法很難實現,更多的時候算法針對某一個問題的痛點才是有效的。

小橙書閱讀指南(十三)——連通性算法(union-find)