定義
並查集是一種樹型的數據結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。常常在使用中以森林來表示。集就是讓每個元素構成一個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合併。
主要操作
1.初始化
把每個點所在集合初始化爲其自身。
通常來說,這個步驟在每次使用該數據結構時只需要執行一次,無論何種實現方式,時間複雜度均爲O(N)。
2.查找
查找元素所在的集合,即根節點。
3.合併
將兩個元素所在的集合合併爲一個集合。
通常來說,合併之前,應先判斷兩個元素是否屬於同一集合,這可用上面的“查找”操作實現。
方法(1)
// 查 O(1)
int find1(int x)
{
return set[x]; //set[x]爲初始化數組
}
//並 O(N)
void Merge1(int a,int b)
{
int i=min(a,b); //取最小的
int j=max(a,b); //取最大的
for(k=1;k<=N;k++)
{
if(set[k]==j)
set[k]=i; //最小的來併合
}
}
缺點:對於合併操作,需要用for循環來搜索全部的元素,複雜,有待改進。
方法(2):每個集合用一棵有根樹來表示。
//查 最壞情況O(N)
int find2(int x)
{
int r=x;
while(set[r]!=r)
r=set[r]; //尋根點
return r;
}
//並 O(1)
void Merge2(int a,int b)
{
if(a>b)
set[a]=b; //取最小的並
else
set[b]=a;
}
優化後的算法
//查 最壞情況O(logN)
int find2(int x)
{
int r=x;
while(set[r]!=r)
r=set[r];
return r;
}
//並 O(1)
int Merge3(int a,int b)
{
if(height(a)==height(b))
{
height(a)=height(a)+1;
set[b]=a;
}
else if(height(a)<height(b))
set[a]=b;
else
set[b]=a;
}
帶路徑壓縮的查找算法
int find3(int x)
{
int r = x;
while (set[r] != r) //循環結束,則找到根節點
r = set[r];
i = x; // r是最終的根節點
while (i != r) //本循環修改查找路徑中所有節點
{
j = set[i];
set[i] = r;
i = j;
}}
例題1:暢通工程
題目描述:
某省調查城鎮交通狀況,得到現有城鎮道路統計表,表中列出了每條道路直接連通的城鎮。省政府“暢通工程”的目標是使全省任何兩個城鎮間都可以實現交通(但不一定有直接的道路相連,只要互相間接通過道路可達即可)。問最少還需要建設多少條道路?
輸入描述:
測試輸入包含若干測試用例。每個測試用例的第1行給出兩個正整數,分別是城鎮數目N ( < 1000 )和道路數目M;隨後的M行對應M條道路,每行給出一對正整數,分別是該條道路直接連通的兩個城鎮的編號。爲簡單起見,城鎮從1到N編號。 注意:兩個城市之間可以有多條道路相通,也就是說3 31 21 22 1這種輸入也是合法的當N爲0時,輸入結束,該用例不被處理。
輸出描述:
對每個測試用例,在1行裏輸出最少還需要建設的道路數目。
Sample Input
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
Sample Output
1
0
2
998
參考代碼
#include<iostream>
using namespace std;
int bin[1002];
int findx(int x)
{ int r=x;
while(bin[r] !=r)
r=bin[r];
return r;
}
void merge(int x,int y)
{
int fx,fy;
fx = findx(x);
fy = findx(y);
if(fx != fy)
bin[fx] = fy;
}
int main()
{ int n,m,i,x,y,count;
while(cin>>n && n)
{
for(i=1;i<=n;i++)
bin[i] = i;
for(cin>>m;m>0;m--)
{ cin>>x>>y;
merge(x,y);
}
for(count=-1, i=1;i<=n;i++)
if(bin[i] == i)
count ++;
cout<<count<<endl;
}
return 0;
}
例題2:小希的迷宮
題目描述:
上次Gardon的迷宮城堡小希玩了很久(見Problem B),現在她也想設計一個迷宮讓Gardon來走。但是她設計迷宮的思路不一樣,首先她認爲所有的通道都應該是雙向連通的,就是說如果有一個通道連通了房間A和B,那麼既可以通過它從房間A走到房間B,也可以通過它從房間B走到房間A,爲了提高難度,小希希望任意兩個房間有且僅有一條路徑可以相通(除非走了回頭路)。小希現在把她的設計圖給你,讓你幫忙判斷她的設計圖是否符合她的設計思路。比如下面的例子,前兩個是符合條件的,但是最後一個卻有兩種方法從5到達8。
輸入描述:
輸入包含多組數據,每組數據是一個以0 0結尾的整數對列表,表示了一條通道連接的兩個房間的編號。房間的編號至少爲1,且不超過100000。每兩組數據之間有一個空行。 整個文件以兩個-1結尾。
輸出描述:
對於輸入的每一組數據,輸出僅包括一行。如果該迷宮符合小希的思路,那麼輸出”Yes”,否則輸出”No”。
Sample Input
6 8 5 3 5 2 6 4
5 6 0 0
8 1 7 3 6 2 8 9 7 5
7 4 7 8 7 6 0 0
3 8 6 8 6 4
5 3 5 6 5 2 0 0
-1 -1
Sample Output
Yes
Yes
N
參考代碼:
#include <iostream>
#include<cstdlib>
struct node
{
int parent;
int weight;
};
node maze[100001];
int visit[100001]; //是集合中的元素都被標記
int findfather ( int i )
{
while ( i != maze[i].parent )
i = maze[i].parent;
return i;
}
void merge ( int a, int b )
{
if ( maze[a].weight == maze[b].weight )
{
maze[b].parent = a;
maze[a].weight = b;
}
else if ( maze[a].weight > maze[b].weight )
maze[b].parent = a;
else
maze[a].parent = b;
}
int main ()
{
int a, b, a1, b1, sign;
while ( cin>>a>>b )
{
memset (visit , 0, sizeof (visit));
int maxn = 0;
int minn = 1000000;
for ( int i = 1; i < 100001; i ++ )
{
maze[i].parent = i;
maze[i].weight = 1;
}
if ( a == -1 && b == -1 ) break;
if ( a == 0 && b == 0 )
{ cout<<"Yes\n"; continue; }
sign = 0;
do {
if ( a < minn ) minn = a;
if ( b < minn ) minn = b;
if ( a > maxn ) maxn = a;
if ( b > maxn ) maxn = b;
visit[a] = visit[b] = 1;
a1 = findfather (a);
b1 = findfather (b);
if ( a1 == b1 ) //節點同根
{ sign=-1; }
else
merge (a1, b1);
cin>>a>>b;
if ( a== 0 && b == 0) break;
}while (1);
if ( sign == -1 )
{ cout<<"No\n"; }
if ( sign == 0 )
{
for (int i = minn; i <= maxn; i ++)
{
if ( visit[i] && maze[i].parent == i )
sign ++;
}
if (sign == 1) cout<<"Yes\n";
else cout<<"No\n";
}
}
return 0;
}