並查集
並查集是一種樹狀結構,他支援兩件事
我們把集合轉化成樹,一顆樹代表一個集合,樹根代表集合的老大,查詢隸屬集合就回傳樹根是誰(一個樹餔可能有兩顆樹根吧),合併的時侯,就把一顆樹的樹根只到另一顆,以下為詳細的描述。
初始
一開始的時候,每個點自成一個集合,所以把樹根都設為自己。
查詢
查詢的時候,要查到樹根為自己的點,為止否則的話就要繼續查。
| int Find(int x)
{
if (x == p[x])
return x;
return find(p[x]);
}
|
狀態壓縮:在合併之後原本被指向的樹根就沒用了,我們可以一邊做查詢時,一邊做更新。
| int Find(int x)
{
if (x == p[x])
return x;
return p[x] = find(p[x]);
}
|
合併
找出兩個點的樹根,將一個樹根合併指到另一個樹根。
| void Union(int a, int b)
{
a = Find(a);
b = Find(b);
if (a == b)
return;
p[a] = b;
}
|
啟發式合併:建立一個 代表樹的高度,亦是元素最大遞迴次數, 一開始為 。再來,我們每次都讓高度小的高度大的合併,如果遇到高度一樣的,就讓合併別人的樹高度加 。如果要把高度變為 ,則至少需要 個點,由此推出 N 個點所形成最高之高度為 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | void Union(int a, int b)
{
a = Find(a);
b = Find(b);
if (a == b)
return;
if (rank[a] < rank[b])
p[a] = b;
else if (rank[a] > rank[b])
p[b] = a;
else
{
p[a] = b;
rank[a]++;
}
}
|
也可以維護並查集的個數,個數大的合併個數小的並查集。
| void Union(int a, int b)
{
a = Find(a);
b = Find(b);
if (a == b)
return;
if (sz[a] < sz[b])
swap(a, b);
sz[a] += sz[b];
p[b] = a;
}
|
完整程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | int p[N], rank[N];
void init()
{
for (int i = 0; i < N; i++)
{
p[i] = i;
rnak[i] = 1;
}
}
int Find(int x)
{
if (x == p[x])
return x;
return p[x] = find(p[x]);
}
void Union(int a, int b)
{
a = Find(a);
b = Find(b);
if (a == b)
return;
if (rank[a] < rank[b])
p[a] = b;
else if (rank[a] > rank[b])
p[b] = a;
else
{
p[a] = b;
rank[a]++;
}
}
|
例題練習