可以理解為使用陣列實現的樹形結構,只儲存了每個節點的父節點(前驅)。
功能為:合併兩個節點(及其所在集合) 、 查詢節點所屬集合的代表節點(可以理解為根節點)。
以6個元素為例(編號0到5):把0單獨劃分為乙個集合;把1,2,3,4劃分為乙個集合;把5單獨劃分為乙個集合。
n個元素的並查集,只需要乙個容量為n的陣列f[n],值全部初始化為自己即可:for(int i=0;i主要**:find(x): if(x == f[x]) return x;
return find(f[x]);
但若只是簡單的這樣做,會出現上圖第三個圓中的情況,即查詢某個節點時遞迴太多次。因此需要「路徑壓縮」,只需增加一步:
find(x): if(x == f[x]) return x;
return f[x] = find(f[x]);
union(x,y): int fx=find(x), fy=find(y);
if(fx != fy) f [fx] = fy; // 此處換為f [fy] = fx也行,道理相同,意義和效果其實也一樣。
注意:一定是f [fx] = fy,而不是f [x] = y。只有把x和y的最終父節點(前驅)連線起來,所屬的兩個集合才算真正完全連通,整個邏輯也才能正確。
n個點有m對關係,把n個節點放入兩個集合裡,要求每對存在關係的兩個節點不能放在同乙個集合。問能否成功完成?
把每個節點擴充套件為兩個節點(一正一反),若a與b不能在一起(在同乙個集合),則把a的正節點與b的反節點放一起,把b的正節點與a的反節點放一起,這樣就解決了a與b的衝突。若發現a的正節點與b的正節點已經在一起,那麼說明之前的某對關係與(a,b)這對關係衝突,不可能同時滿足,即不能成功完成整個操作。
n個點,每個點擴充套件為兩個點(一正一反),則需要乙個容量為2*n的陣列f[n],值全部初始化為自己即可:for(int i=0;i<2*n;i++) f[i]=i;
(注意初始編號,若編號為[1,n],則初始化應該為:for(int i=1;i<=2*n;i++) f[i]=i;)
乙個點x的正點編號為x,反點編號為x+n(這樣每個點的反點都是+n,規範、可讀性強、不重複、易於理解)。
1)初始化2*n個節點的初始父節點,即它本身。
2)遍歷m對關係,對每對(a,b),先找到a和b的父節點,若相等則說明(a,b)的關係與之前的關係有衝突,不能同時解決,則得到結果:不能完成整個操作。
否則執行:union(a, b+n), union(b, a+n). (這時已經find過了,直接執行f [fx] = fy這一句就等效與union(x, y) )
3)若m對關係都成功解決,則得到結果:能夠完成整個操作。
由於擴充套件域會直接使陣列容量翻倍,所有一般只解決這種「二分」問題,只擴充套件為2倍即可。
優點在於:結構簡單,並查集的操作也不需要做改變,非常易於理解。 缺點顯然就是:需要額外儲存空間。
n個節點有m對關係(m條邊),每對關係(每條邊)都有乙個權值w,可以表示距離或劃分成多個集合時的集合編號,問題依然是判斷是否有衝突或者有多少條邊是假的(衝突)等。
給n個節點虛擬乙個公共的根節點,增加乙個陣列s[n]記錄每個節點到虛擬根節點的距離,把x,y直接的權值w看為(x,y)的相對距離。
union(x,y,w)時額外把x,y到虛擬根節點的距離(s值)的相對差值設定為w;find(x)時,壓縮路徑的同時把當前s值加上之前父節點的s值,得到真實距離。
f[n]陣列記錄節點的父節點,s[n]陣列記錄節點到虛擬根節點的距離: for(int i=0;iif(x==f[x])return x;
int t = f[x];
f[x] = find(f[x]);
s[x] += s[t];
// s[[x] %= mod; 若s[x]表示劃分成mod個集合時的集合編號等情況時,則需要求餘。
return f[x];
int fx = find(x), fy = find(y); //此時已經s[x]和s[y]都已經計算為真值。
if(fx != fy) e[maxm];
bool
cmp(edge a, edge b)
int f[2 *maxn];
int find(int
x) int
main()
if (i == m)printf("0"
);
else printf("%d"
, e[i].c);
return0;
}擴充套件域的並查集解法
#include #include加權並查集解法#include
using
namespace
std;
typedef
long
long
ll;const
int maxn = 200000 + 10
;const
int gxs = 2; //
模2就只有0,1兩個值,分別代表兩個不同的集合
const
int mod = 2
;int
n, m;
int f[maxn], s[maxn]; //
f記錄父節點(前驅),s記錄到虛擬root點的距離
void
init()
//查詢
int finds(int
x) //
新建關係
void unions(int x, int y, int
w) }
struct
node
};vector
que;
intmain() );
}sort(que.begin(), que.end());
//從大到小排序
init();
for (int i = 0; i < m; i++)
//否則說明解決之前的衝突後,當前衝突也被解決。
}
else
}cout
<< 0
<
return0;
}
並查集 並查集
本文參考了 挑戰程式設計競賽 和jennica的github題解 陣列版 int parent max n int rank max n void init int n int find int x else void union int x,int y else 結構體版 struct node ...
並查集高階
n 個孤立點,m 次加邊操作,設 l i,j 表示結點 i 和 j 最早在第 l i,j 次操作後連通。在 m 次操作後,求出 sum n sum n l i,j 的值。解法 quad 並查集的時候記錄子樹大小即可。n 個孤立點,m 次加邊操作,q 次詢問,每一次問 u 和 v 最早在第幾次操作後連...
並查集 帶權並查集詳解
contents 一.引入 二.查詢函式與集合合併函式 偽 三.權值如何更新 從圖的角度理解 並查集實際上是一顆由若干個樹構成的森林,而如果還需要儲存邊的權值,則需要額外用乙個陣列d記錄每個結點和其父節點的權值 從集合的角度理解 並查集儲存的資訊是某點屬於哪個集合,而帶權並查集還能記錄某點和其父結點...