算法1之並查集
並查集:即包含 合併集合
和 查找集合中的元素
兩種操作的樹型數據結構, 並
、查
、集
三個字已經涵蓋了其功能。常常在使用中以森林來表示。
一、並查集思想
1.1 算法思想
如果將集合中的元素當成是樹上的不同的節點,那麼,判斷兩個元素是否屬於同一個集合的問題,就變成了他們所在樹的根是否爲同一根的問題。
1.2 路徑壓縮
同一棵樹存在不同的枝幹,枝幹上有不同層次的葉節點(即我們所說的元素)。爲了查快查找某個葉子節點的根節點的速度,查找時將元素 x 到根節點 root 上的所有葉子節點的 Parent(父節點)設爲根節點。稱該優化的方法爲路徑壓縮。
經過路徑壓縮優化後,平均複雜度可視爲 Ackerman 函數的反函數,實際應用中可粗略認爲其是一個常數。
1.3 問題描述
以親戚爲例,或許你不會知道,某個朋友是你的親戚,他可能是你的曾祖父的外公的女婿的外甥女的表姐的孫子。
通過家族圖譜,可以很輕鬆判斷兩個人是否爲親戚。但當兩個人的最近公共祖先與他們相隔好幾代,使得家族十分龐大,那麼檢驗兩人的親戚關係就會變得很困難。通過寫程序推出 Marry 和 Ben 是否爲親戚。
說明: 這是一個非常經典的並查集例子。用集合的思路,對每個人建立一個集合,開始的時候集合元素就是這個人本身,表示開始時,不知道任何人是他的親戚。以後,每次給出一個親戚關係時,就開始進行集合合併。這樣實時地獲取到當前狀態下的集合關係。
1.4 主要操作
並查集主要操作爲:一初始、二合併、三查找、四判斷
- 初始化並查集(InitUnionFind)
- 合併兩個不相交的集合(Union)
兩個元素,分別找到他們的根節點,然後將其中一個元素的根節點的父親指向另外的一個元素的根節點。 - 查找某元素的根節點(Find)
查找一個元素的根節點,parent--->parent--->parent.....
。當查找元素根節點途徑的元素很多時,使用路徑壓縮 優化算法。直接將該元素的父親指向根節點或者祖先。 - 判斷兩個元素是否屬於同一集合(isConnected)
判斷兩個元素是否屬於同一個集合,就是分別找到他們的根節點,然後判斷兩個根節點是否相等。
二、並查集例子
2.1 暢通工程
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 70879 Accepted Submission(s): 37912
Problem Description
某省調查城鎮交通狀況,得到現有城鎮道路統計表,表中列出了每條道路直接連通的城鎮。省政府“暢通工程”的目標是使全省任何兩個城鎮間都可以實現交通(但不一定有直接的道路相連,只要互相間接通過道路可達即可)。問最少還需要建設多少條道路?
Input
測試輸入包含若干測試用例。每個測試用例的第1行給出兩個正整數,分別是城鎮數目N ( < 1000 )和道路數目M;隨後的M行對應M條道路,每行給出一對正整數,分別是該條道路直接連通的兩個城鎮的編號。爲簡單起見,城鎮從1到N編號。
注意:兩個城市之間可以有多條道路相通,也就是說
3 3
1 2
1 2
2 1
這種輸入也是合法的
當N爲0時,輸入結束,該用例不被處理。
Output
對每個測試用例,在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
Hint
Hint
Huge input, scanf is recommended.
Source
浙大計算機研究生複試上機考試-2005年
Recommend
JGShining
#include<iostream>
#include<stdio.h>
using namespace std;
int pre[1010];
int unionSearch(int root)
{
int son,tmp;
son = root;
/*查找根節點*/
while(root!= pre[root])
root = pre[root];
/*路徑壓縮*/
while(son!= root)
{
tmp = pre[son];
pre[son] = root;
son = tmp;
}
return root; // 返回跟界定啊
}
int main()
{
int num, road, total,start, end, root1, root2;
while(scanf("%d", &num) && num && scanf("%d", &road))
{
total = num-1;// num-1 個集合(元素);
for(int i=1; i<=num; i++)
{
pre[i] = i; //1.初始化並查集,此時每個元素自己都是根節點
}
while(road--)
{
scanf("%d%d", &start, &end); //2.不同集合根據路徑,開始合併
root1 = unionSearch(start); //3.查找根節點,路徑壓縮
root2 = unionSearch(end);
if(root1 !=root2) //4.判斷,根節點不一樣
{
pre[root1] = root2; //選出一個當根節點
total--; //集合少一個,關係少一個
}
}
cout<<total<<endl; //計算剩餘的集合
}
return 0;
}
【推薦】