1. 程式人生 > 其它 >高階資料結構:並查集

高階資料結構:並查集

並查集(Union-find Sets)是一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。

並查集

並查集(Union-find Sets)是一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。

並查集的思想是用一個數組表示了整片森林(parent),樹的根節點唯一標識了一個集合,我們只要找到了某個元素的的樹根,就能確定它在哪個集合裡。

yxc模板

並查集 —— 模板題:AcWing 836. 合併集合, AcWing 837. 連通塊中點的數量

(1)樸素並查集:

int p[N]; //儲存每個點的祖宗節點

// 返回x的祖宗節點
int find(int x)// 路徑壓縮
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定節點編號是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;

// 合併a和b所在的兩個集合:
p[find(a)] = find(b);  // 不要寫成p[a] = b;

(2)維護size的並查集:

int p[N], size[N];
//p[]儲存每個點的祖宗節點, size[]只有祖宗節點的有意義,表示祖宗節點所在集合中的點的數量

// 返回x的祖宗節點
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定節點編號是1~n
for (int i = 1; i <= n; i ++ )
{
    p[i] = i;
    size[i] = 1;
}

// 合併a和b所在的兩個集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);

(3)維護到祖宗節點距離的並查集:

int p[N], d[N];
//p[]儲存每個點的祖宗節點, d[x]儲存x到p[x]的距離

// 返回x的祖宗節點
int find(int x)
{
    if (p[x] != x)
    {
        int u = find(p[x]);
        d[x] += d[p[x]];
        p[x] = u;
    }
    return p[x];
}

// 初始化,假定節點編號是1~n
for (int i = 1; i <= n; i ++ )
{
    p[i] = i;
    d[i] = 0;
}

// 合併a和b所在的兩個集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根據具體問題,初始化find(a)的偏移量

原理介紹

參考:演算法訓練營進階篇。

假設存在一個龐大的部落,人數眾多,我們需要判斷給出的兩人是否具有親戚關係。

現給定親戚關係的兩條重要性質:

  1. 傳遞性:若x、y是親戚,y、z是親戚,那麼x、z是親戚;
  2. 集合合併(連通集合):若x、y是親戚,則x的親戚也是y的親戚,y的親戚也是x的親戚。

我們可以用並查集來處理具有這兩種性質的親戚關係的判定問題。

演算法步驟:1.初始化;2.查詢;3.合併。

y總的模板真的很不錯,hh。

還是直接上例題。

初始化時並查集編號從1開始!

洛谷P3367並查集(模板題)

題目描述
如題,現在有一個並查集,你需要完成合並和查詢操作。

輸入格式
第一行包含兩個整數 N,M,表示共有 N 個元素和 M 個操作。

接下來 M 行,每行包含三個整數 Z,X,Y
當 Z=1 時,將 X與Y所在的集合合併。
當 Z=2 時,輸出 X與Y是否在同一集合內,是的輸出 Y ;否則輸出 N 。

輸出格式
對於每一個 Z=2 的操作,都有一行輸出,每行包含一個大寫字母,為 Y 或者 N 。

輸入輸出樣例
輸入
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
輸出
N
Y
N
Y

對於 100\%100% 的資料,1≤N≤10^4 ,1≤M≤2×10^5
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100010;

int p[N];// 儲存每個點的祖宗結點
int n,m;

int find(int a){// 遞迴找到祖宗
    if (p[a] != a) p[a] = find(p[a]);
    return p[a];
}

int main(){
    scanf("%d%d",&n,&m);
    // 初始化編號
    for (int i = 1;i <= n;i++) p[i] = i;

    int z,x,y;char res;
    while (m--){
        scanf("%d%d%d",&z,&x,&y);
        if (z == 1) p[find(x)] = find(y);// 合併集合操作
        else{
            res = find(x) == find(y) ? 'Y' : 'N';
            printf("%c\n",res);
        }
    }
    return 0;
}

初始化:剛開始的時候每個點就是一個獨立的集合,且這個集合樹的樹根就是本身。

查詢:路徑壓縮,每次執行find操作的時候,把訪問過的節點(也就是所有元素的父親),都統統指向樹根祖宗.這種方法可以避免出題人刻意卡掉鏈式結構。

N次合併M次查詢的時間複雜度為:O(M*Alpha(N)),Alpha是Ackerman函式的某個反函式,在很大範圍內它的值不超過4,接近線性,所以可以近似看成是:O(1)。