1. 程式人生 > >並查集總結(路徑壓縮+啟發式合併)

並查集總結(路徑壓縮+啟發式合併)

並查集

一、並查集是處理什麼問題的:

並查集,是一種用來管理元素分組情況的資料結構,可以處理一些不相交集合的合併與查詢問題;它可以進行合併操作,但不能進行分割操作。


二、兩大操作:


(1)查詢元素a和元素b是否屬於同一集合;


(2)合併元素a和元素b所在的集合;

三、主要的步驟:

初始化:把每個點所在集合初始化為其自身。(通常來說,這個步驟在每次使用該資料結構時只需要執行一次,無論何種實現方式,時間複雜度均為O(N)。)

查詢:查詢元素所在的集合,即根節點。

合併:將兩個元素所在的集合合併為一個集合。

基本思想:
在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查詢一個元素在哪個集合中,最終就形成了多個(可能一個)不同的集合,其中每一個集合都具有不同於其它集合的屬性,例如:每一個集合可以表示一個連通分支,則屬於同一個集合的元素就表示這個連通分支的頂點,這個集合中的每一個頂點都是相互可達的,這樣就可以就可以解決任意輸入的兩個元素是否可達的問題了。

四、處理並查集問題需要解決了的幾個難點:

(1)如何合併兩個不相交集合;
(2)如何判斷兩個元素是否屬於同一個集合;
(3)路徑壓縮,優化時間。

優化方法:1、路徑壓縮:對每一個節點,一旦向上走到了一次根節點,就把這個節點到父親的邊改為直接連向根的邊;
                  2、啟發式合併:讓讓深度較小的樹成為深度較大的樹的子樹。


五、並查集的結構:
並查集是使用樹形結構實現的,不過,不是二叉樹。每個元素對應一個節點,每個集合對應一棵樹。


六、並查集實現中的注意點:(在使用路徑壓縮時,為了方便起見,即使樹的高度發生了變化,我們也不修改rank的值)

在樹形資料結構裡,如果發生了退化的情況,那麼複雜度就會變得很高。因此,有必要想辦法避免退化的發生。在並查集中,只要按照如下方法就可以避免退化。

(1)對每棵樹,記錄這棵樹的高度(rank).

(2)合併時如果兩棵樹的rank不同,那麼從rank小的向rank大的連邊。

我做的第一題並查集   HDU 1213;

#include <iostream>
#include <stdio.h>
#include <string.h>
#define N 1000

using namespace std;

int father[N];
int num;

void set_mark(int n)
{
    int i;
    for(i = 1; i <= n; i++)
    {
        father[i] = i;
    }
    return;

}

int getfather(int v)
{
    int geng = v;
    int temp;
    while(geng != father[geng])
    {
        geng = father[geng];
    }
    while(geng != v)
    {
        temp = father[v];
        father[v] = geng;
        v = temp;
    }
    return geng;

}

void Merge(const int &i, const int &j)
{
    int x = getfather(i);
    int y = getfather(j);
    if(x != y)
    {
        father[x] = y;
        num -= 1;
    }
    return;
}


int main()
{
    int T;
    int n, m;
    cin >> T;
    while(T--)
    {

        int a, b;
        cin >> n;
        num = n;
        set_mark(n);
        cin >> m;
        for(int i = 0; i < m; i++)
        {
            cin >> a;
            cin >> b;
            Merge(a, b);
        }
        cout << num << endl;
    }
    return 0;
}


以下每一塊是一個模板(分四個函式標記、找根、合併、查詢)

int father[maxn];
int n;

void Init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
    }
}

// 遞迴法
int getFather(const int &v) {
    if (father[v] == v)
        return v;
    else
        return getFather(father[v]);
}

// 遞迴另一種寫法。
int getFather(const int &v) {
    if (father[v] != v) {
        int root = getFather(father[v]);
        return father[v] = root;
    }
    else
        return v;
}

// 非遞迴,且不帶路徑壓縮。
int getFather(const int &v) {
    int r = v;
    while(r != father[r])
        r = father[r];
    return r;
}
*/

// 非遞迴,路徑壓縮
int getFather(const int &v) {
    int t1 = v, t2;
    while (v != father[v])
        v = father[v];
    while (t1 != father[t1]) { // 沿途上所有結點的父親改成根。這一步是順便的,不增加時間複雜度,卻使得今後的操作比較快。這個優化稱為路徑壓縮。
        t2 = father[t1];
        father[t1] = v;
        t1 = t2;
    }
    return v;
}


// 歸併:把節點i、節點j放到同一個根底下。
void merge(const int &i, const int &j) {
    int x = getFather(i);
    int y = getFather(j);
    if (x != y) // 可選,主要是為了防止getFather()路徑壓縮的時候出現死迴圈。
        father[x] = y; // 有向圖注意順序,該行程式碼含義:a->b。
}

// 查詢:查詢節點i跟節點j是否在同一根下。
bool judge(const int &i, const int &j) {
    if (getFather(i) == getFather(j))
        return true;
    else
        return false;
}

優化思路:

 merge函式可以採用啟發式合併,思路就是把深度較小的那顆子樹併到深度較大的那顆子樹上。

int father[maxn];
int n;

void Init() {
    for (int i = 0; i <= n; ++i) {
        rank[i] = 0;
        father[i] = i;
    }
}

int getFather(const int &x) {
    int px = x , i ;
    while ( px != father[px])   // find root
        px = father[px]; 
    while ( x != px ) {         // path compression
        i = father [ x ];
        father [ x ] = px ;
        x = i;
    }
    return px ;
}

void merge(const int &x, const int &y) { // 下面還有一種寫法
    x = getFather(x);
    y = getFather(y);
    if (rank[x] > rank[y])
        father[y] = x;
    else {
        father[x] = y;
        if (rank[x] == rank[y])
            rank[y]++;
    }    
}

bool judge(const int &i, const int &j) {
    if (getFather(i) == getFather(j))
        return true;
    else
        return false;
}

int father[maxn], rank[maxn];

void Init(const int &v) {
    father[v] = -1;
    rank[v] = 0;
}

int getFather(const int &v) {
    int t1 = v;
    while (father[t1] != -1)
    t1 = father[t1];
    while (v!=t1) {
        int t2 = father[v];
        father[v] = t1;
        v = t2;
    }
    return t1;      
}    

void merge(const int &a, const int &b) {
    int t1 = getFathet1(a);
    int t2 = getFathet1(b);
    if(rank[t1] > rank[t2])
        father[t2] = t1;
    else
        father[t1] = t2;
    if(rank[t1] == rank[t2])
        ++rank[t2];     
}

bool judge(const int &i, const int &j) {
    if (getFather(i) == getFather(j))
        return true;
    else
        return false;

}
/*
    另一種寫法:
*/

int f[maxn], rank[maxn], num[maxn];

void Init() {
    for (int i = 0; i <= n; ++i) {
    rank[i] = 1;
    num[i] = 1;
    father[i] = i;
    }
}

// f[]陣列存放根節點,rank[]陣列來存放根節點的深度,num[]陣列來存放節點個數,rank[]陣列和num[]陣列的初始化都應為1

// 啟發式合併:
void merge(int x, int y)
{
    fx = getFather(x);
    fy = getFather(y);
    if (fx == fy) return;
    if (rank[fx] > rank[fy]) {
        father[fy] = fx;
        num[fx] += num[fy];
    }
    else {
        father[fx] = fy;
        num[fy] += num[fx];
        if (rank[fx] == rank[fy]) {
            ++rank[fy];
        }
    }
}

// 路徑壓縮:
int getFather(int x) {
    if(father[x] == x)
        return x;
    else
        return father[x] = getFather(father[x]);
}


// 仍有一種寫法:

int father[maxn];

void Init() {
    for(int i = 0; i < n; ++i)
        father[i] = -1;
}

int getFather(int x) {
    if (father[x] < 0)
        return x;
    father[x] = getFather(father[x]);
    return father[x];
}

int getFather(int x) {
    int p = x, t;
    while (father[p] >= 0)
        p = father[p];
    while (x != p) {
        t = father[x];
        father[x] = p;
        x = t;
    }
    return x;
}
void merge(int x, int y) {
    x = getFather(x);
    y = getFather(y)
    if (x == y) return;
    if (father[x] < father[y]) {
        father[x] += father[y];
        father[y] = x;
    } else {
        father[y] += father[x];
        father[x] = y;
    }
}

bool judge(const int &i, const int &j) {
    if (getFather(i) == getFather(j))
        return true;
    else
        return false;
}