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

上一章我大概說明了什麼是圖論以及無向圖的基礎概念,本章咱們要研究一種更廣泛的算法——連通性算法。它屬於圖論的分支,也是一種抽象算法。在深刻算法以前,咱們先提出一個具體的問題:假設在空間中存在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();
}

1、快速查詢算法Quick-Findide

咱們將空間中的點這一律念抽象成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算法通常沒法處理大型數組。ui

算法圖示:spa

2、快速鏈接算法Quick-Unioncode

咱們要討論的下一個算法的重點是提升union()方法的速度,爲此可能會稍微犧牲一下find()的效率,可是一般狀況下這樣作是值得的。Quick-Union算法考慮把屬於同一組連通份量的點鏈接成一棵樹,i表明點,int[i]表明i的父節點,根節點p等於int[p]。blog

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

快速鏈接算法的每一次鏈接會分別遍歷兩次連通份量,在連通份量中包含元素數量相對總數而言比較小的狀況下能夠提供很是不錯的速度。接口

算法圖示:io

 

事實上,不管是Quick-Find算法仍是Quick-Union算法,他們在圖論的基礎上基本是起到相互補充的做用。更重要的一點是,咱們經過對他們的學習能夠認識到,十全十美的算法很難實現,更多的時候算法針對某一個問題的痛點纔是有效的。

相關文章
相關標籤/搜索