先來想一想「親戚」這個詞的定義:「指和本身有血親和姻親的人」。你和你女朋友家眷自己並不是是親戚關係,一旦結婚後,兩家人便成爲了一家人,你的家人包括你在內和你女朋友及其家人自動成爲了親戚,這就是一個典型的並查集應用。並查集是一種樹形的數據結構,用於處理一些不相交集合的合併及查詢,上面例子中「結婚」其實就是並查集的合併操做
下面咱們來演示下並查集的常規操做,咱們默認建立6個元素,這6個元素咱們能夠當作是互不相交的6個集合node
咱們能夠把並查集當作是由不少顆樹組成的森林,每棵樹中相連的結點都表明屬於同一集合,樹中parent指向本身的根結點被視爲該集合的表明。初始的時候,咱們用一個parent數組存儲全部結點的父結點下標,因爲默認狀況下每一個集合互不相交,因此咱們令每一個結點的parent都指向本身,這樣就生成了N棵以本身爲根的樹組成的森林。ios
parent數組的初始化結構以下圖所示:
git
初始化並查集的代碼:github
void make_set (){
for (int i=0;i<N;i++){
parent[i] = i;// 如上圖i的parent指向本身
}
}
複製代碼
Union(a,b)會將a所在的集合與b所在的集合相結合。在數據結構的實現上,只須要將b的根結點指向a的根結點,或a的根結點指向b的根結點便可,本文中默認使用前者。假設咱們如今要將0,2,4合併爲一個集合,1,3 合併爲一個集合,5單獨視爲一個集合,那麼運算的過程的可能以下:
數組
合併0和2,需將2指向0。bash
接着,若是想要繼續Union(5,3),咱們能夠先得到結點3所處樹的根結點1,讓1指向5便可。可是這樣樹的高度要比5指向1的樹要高,隨着並查集規模的增大,樹會多出不少沒必要要的高度,這將致使並查集的查詢更耗時。
數據結構
爲了讓合併後樹的總體高度相對更矮,在每次合併時,咱們讓高度較矮的樹併入高度較高的樹,這種優化會在以後的代碼中體現出來。
函數
最後,若是咱們想要Union(2,3),因爲2,3各自所處的樹高度相同,因此按默認方式將「3」的根結點「1」指向「2」的根結點「0」便可。
優化
在實現Union函數以前,咱們先增長一個rank[N]數組記錄高度,默認的時候rank數組所有設置爲0,rank中數值隨着並查集的合併而改變。下面給出Union的代碼:ui
void union_set(int a,int b) {
if (a==b) return; // 相同
int root_a = find_root(a);//找到a的根結點
int root_b = find_root(b);//找到b的根結點
if (root_a == root_b) return; //根結點相同
int higher_root = rank[root_a]>rank[root_b] ?root_a:root_b;// 選出較高的樹
int lower_root = rank[root_a]<rank[root_b]?root_a:root_b;// 選出較低的樹
if( higher_root == lower_root ) {
// 兩顆樹高度相等的狀況
parent[root_b] = root_a; //root_b.parent 指向 root_a (默認操做)
++rank[root_a];// 高度+1
}else {
parent[lower_root] = higher_root; // 較矮的樹指向較高的樹,不會改變總體高度
}
}
複製代碼
查詢某個元素所在的集合很是簡單,因爲parent數組記錄了每個元素的父結點,咱們只須要遞歸回溯便可。
執行find_root(5)
後沿着紅線向上回溯找到0,執行find_root(2)
後沿着紅線向上回溯也找到了0,說明5和2同屬一個集合,而執行find_root(7)
後沿着紅線回溯找到了6,故7和元素5,2不屬於同一個集合。下面給出實現代碼:
int find_root(int node) {
if (parent[node] == node) return node;
return find_root(parent[node]);
}
複製代碼
在查詢某個元素的所在集合的時候,上面的find_root(int node)
函數會返回元素所在的樹的根結點------這個集合的表明,在這個過程當中,咱們能夠將當前待查找的元素直接指向這個根結點,下降樹的高度,從而使得查詢速度獲得提高。以上圖爲例子,執行find_root(3)
,find_root(5)
後樹形結構會變成以下結構:
代碼實現上的改動很是小:
int find_root(int node) {
if (parent[node] == node) return node;
parent[node] = find_root(parent[node]); // 指向根結點
return parent[node];
}
複製代碼
並查集的代碼和邏輯都很是精簡,在我看來是很是優雅的數據結構。
#include<iostream>
#include <stdio.h>
#define N 10000+10
int parent[N];
int rank[N];
void make_set (){
for (int i=0;i<N;i++){
parent[i] = i;
rank[i] = 0;
}
}
int find_root(int node) {
if (parent[node] == node) return node;
parent[node] = find_root(parent[node]);
return parent[node];
}
void union_set(int a,int b) {
if (a==b) return; // 相同
int root_a = find_root(a);//找到a的根結點
int root_b = find_root(b);//找到b的根結點
if (root_a == root_b) return; //根結點相同
int higher_root = rank[root_a]>rank[root_b] ?root_a:root_b;// 選出較高的樹
int lower_root = rank[root_a]<rank[root_b]?root_a:root_b;// 選出較低的樹
if( higher_root == lower_root ) {
// 兩顆樹高度相等的狀況
parent[root_b] = root_a; //root_b.parent 指向 root_a (默認操做)
++rank[root_a];// 高度+1
}else {
parent[lower_root] = higher_root; // 較矮的樹指向較高的樹,不會改變總體高度
}
}
int main(){
make_set();
union_set(0, 2);
union_set(2, 4);
union_set(1, 3);
union_set(5, 3);
union_set(2, 3);
find_root(3);
find_root(5);
for(int i=0;i<6;i++) {
printf("%d ",parent[i]);// 輸出全部指向
}
}
複製代碼
PAT- 1118 Birds in Forest
PAT- 1118 Birds in Forest--代碼
本篇已同步到我的博客:優雅的數據結構–並查集