1. 程式人生 > >基礎演算法與資料結構(三)普通並查集

基礎演算法與資料結構(三)普通並查集

簡介

在平時的計算中,常常會遇到集合劃分的問題,例如一個集合S={a1,a2,a3,a4},按照一定規則我們可以劃分為S1={a1,a2},S2={a3},s3={a4}。但是在劃分好集合後,又該如何快速確認任意兩個元素之間的關係呢。由此引出並查集。


並查集簡介

並查集最關鍵的表現就是一個集合中的每一個元素都有一個共同的集合代表元素,也就是集合的boss。判斷兩個元素是否為同一個集合的條件便是二者的boss是否為一個人。而合併兩個集合,只需要將兩個集合所有元素的最終boss都設成同一個元素。
由此:並查集需要兩個關鍵函式

  • find()查詢元素集合
  • union()合併集合

先用圖感受一下思路:
1.現在每個元素都是一家公司,它們各自都是自己的boss。


2.公司1和公司5志趣相投,決定合作。一番商討,決定5來當boss。


3.公司3和公司4見狀,兩手一拍,不行!我們也得合作和那兩傢伙幹,3當了boss。


4.公司6看腥風血雨,正好和公司4有點交情,想來尋求庇護,4成為了6的boss,3成為了第一個大boss。


5.底下其樂融融,3和5左思右想覺得再這麼對著幹下去不如來一波和諧的py交易,說時遲那時快,3,5決定合體!但boss只能有一個,3有管理經驗,3變成了5的boss。


6.公司2的訊息不靈通,市場鉅變,還在自嗨,反應過來的時候,想著去求助1和6,二者告訴他他們的boss分別是5和4,2又去找5和4,發現他們的boss都是3。哦!原來都是一家人,3啊,我要做你小弟啊!


PS:然後他們就因為壟斷被罰款了。


程式碼實現

就先來實現最簡單的。

定義

int boss[1000];//這個陣列記錄的每個公司的boss是誰

Find

int find(int x) {
    while (boss[x] != x) x = boss[x];//如果x的boss不是x自己,那麼就去找x的boss
    return x;
}

Union

void union(int x, int y) {
    int tx = find(x);//找到x的boss
    int ty = find(y);//找到y的boss
    if (x != y) boss[x] = y;//不是一個boss,你就當我的boss
    else return;
}

一個簡單的並查集就實現了,但是這樣實現的並查集樹狀結構不可控,一個一字長蛇陣就可以讓查詢的效率變得很低,所以我們希望所有子公司可以直接找到最終的boss。這就是路徑壓縮演算法。

我們先修改find函式就可以了

Find_zwei

int find_zwei(int x) {
    if (boss[x] == x) return x;
    return boss[x] = find_zwei(boss[x]);//你的boss就是我的boss,my~my~my~my~my~
}

我們還可以改造union函式,進一步優化這個樹狀結構。我們引入一個rank陣列表示每一個元素的高度,初始為1。

定義_zwei

int boss[1000];
int rank[1000];

Union_zwei

void union_zwei(int x, int y) {
    int tx = find_zwei(x);
    int ty = find_zwei(y);
    if (tx == ty) return;
    if (rank[tx] > rank[ty]) boss[ty] = tx;//tx的高度大於ty的高度,也就是tx為2,ty為1的時候
    else {
        if (rank[tx] == rank[ty]) {//二者高度相同的時候必然會增加高度
            rank[ty]++;
        }
        boss[tx] = ty;
    }
}

初始化

void init(int n) {
    for (int i = 1; i <= n; i++) {
        boss[i] = i;
        rank[i] = 1;
    }
}

至此,就是普通並查集。後續再講帶權並查集。