叫做並查集的主要原因是該資料結構的主要操作是:
1:合併區間(
union)
2:查詢元素所屬區間(
find)
所以叫做並查集
如果給出各個元素之間的聯絡,要求將這些元素分成幾個集合,每個集合中的元素直接或間接有聯絡。在這類問題中主要涉及的是對集合的合併和查詢,因此將這種集合稱為並查集。
問題:若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。 規定:x和y
是親戚,y和
z是親戚,那麼x和
z也是親戚。如果
x,y是親戚,那麼
x的親戚都是
y的親戚,
y的親戚也都是
x的親戚。
要解決這個問題或許可以使用圖論,最後判斷兩個人是否在乙個連通子圖中,但是考慮到複雜度等我們發現這樣並不好處理並且當資料很多的時候會很費時間。這時候我們考慮使用並查集。
就動態連通性這個場景而言,我們需要解決的問題可能是:
給出兩個節點,判斷它們是否連通,如果連通,不需要給出具體的路徑
給出兩個節點,判斷它們是否連通,如果連通,需要給出具體的路徑
就上面兩種問題而言,雖然只有是否能夠給出具體路徑的區別,但是這個區別導致了選擇演算法的不同,本文主要介紹的是第一種情況,即不需要給出具體路徑的union-find
演算法,而第二種情況可以使用基於
dfs的演算法。
思想基本都體現在下面的一步一步改進的**中了,請仔細看。
//簡單陣列,也可看做是使用了鍊錶的陣列化
//#include //#include //#include //#include //#include //#include //#include //#include //#include //using namespace std;
////const int maxn = 1000005;
//int a[maxn]; //a[i]表示的是結點i的組號,初始值是
//int n,m,coun;//count記錄一共有多少組
////int find(int ind)
////
//void unio(int p,int q)
//// coun--;//組數減少1
//}//
//int main()
//// for(int i = 0 ; i < m ; i++)
//
// int t = 4;
// while(t--)
//
// }
// return 0;
//}最簡單的並查集,可以看到unio函式每次更新結點所屬的組號的時候
時間複雜度是o(n),這樣總的時間複雜度就是o(m*n),可能會超時
使用樹形結構
//#include //#include //#include //#include //#include //#include //#include //#include //#include //using namespace std;
////const int maxn = 1000005;
//int a[maxn]; //a[i]表示的是結點i的組號,初始值是
//int n,m,coun;//count記錄一共有多少組
////int find(int p)
////
//void unio(int p,int q)
////
//int main()
//// for(int i = 0 ; i < m ; i++)
//
// int t = 4;
// while(t--)
//
// }
// return 0;
//}//樹形結構容易出現極端情況,即所有的子節點在一行形成乙個鍊錶
//這時候在查詢的時候會很浪費時間。
//其實現在這顆樹已經很節減了,但我們永遠要追求更完美,那麼怎麼樣才能使
//結果更完美呢,我們看到現在我們所有的操作的時間都花費在查詢函式上面了,
//那查詢函式還可以在壓縮一下時間嗎,這腰取決於這顆樹是怎麼樣的。假如每一
//顆樹的深度都是1,也就是說所有的結點都直接掛在根結點上面,那麼這棵樹
//是不是查詢起來很方便了。但這是理想狀態,很難達到,但是我們可以盡可能的
//使數的深度更小。
#include #include #include #include #include #include #include #include #include using namespace std;
const int maxn = 1000005;
int a[maxn]; //a[i]表示的是結點i的組號,初始值是
int s[maxn]; //s[i]表示的是結點i所屬的組中結點的個數(數的大小)
int n,m,coun; //count記錄一共有多少組
int find(int p)
return p;//查詢p的組號(根節點)
//滿路徑壓縮(full compresses paths):這是一種極其簡單但又很常用的方法。
//就是在新增另乙個集合的時候,把所有遇到的結點都指向根節
// if(p!=a[p])
// a[p]=find(a[p]);
// //在遞迴回來的時候把路徑上元素的父親指標都指向根結點。
// return a[p];
}void union(int p,int q)
////void unio(int p,int q)
// return p;//查詢p的組號(根節點)
//滿路徑壓縮(full compresses paths):這是一種極其簡單但又很常用的方法。
//就是在新增另乙個集合的時候,把所有遇到的結點都指向根節
// if(p!=a[p])
// a[p]=find(a[p]);
// //在遞迴回來的時候把路徑上元素的父親指標都指向根結點。
// return a[p];
}//一顆樹要是連通並且沒有環,最終的結果應該是結點的個數等於邊的數目+1
void union(int p,int q)
{ int proot=find(p);
int qroot=find(q);//獲得p和q的組號
if(proot==qroot) return ; //此時說明這棵樹有環
//將小數作為大數的子樹
//按秩進行合併
if(s[proot]
資料結構之並查集
並查集 union find sets 是一種簡單的用途廣泛的集合.並查集是若干個不相交集合,能夠實現較快的合併和判斷元素所在集合的操作,應用很多,如其求無向圖的連通分量個數 最小公共祖先 帶限制的作業排序,還有最完美的應用 實現kruskar演算法求最小生成樹。其實,這一部分 演算法導論 講的很精...
資料結構之並查集
覺得很不錯的參考資料引用一下 陣列實現 合併操作代價高,可達o n 2 鍊錶實現 樹結構實現 查詢與合併的平均時間複雜度為o log 2 n 與樹的深度有關,優化 降低時間複雜度 按秩合併 若h b h b,則將b樹作為a樹的子樹。帶路徑壓縮的查詢演算法 改變結點所指方向以減小深度,查詢路徑時 走兩...
資料結構之並查集
1.將兩個集合合併。2.詢問兩個元素是否在乙個集合當中。複雜度近乎o 1 基本原理 每個集合用一棵樹來表示。樹根的編號就是整個集合的編號。每個節點儲存它的父節點,p x 表示x的父節點。問題1 如何判斷樹根 if p x x 問題2 如何求x的集合編號 while p x x x p x 問題3 如...