普通並查集:
#define MAX_SIZE 100005 int pa[MAX_SIZE]; //儲存有向圖的邊void init() //初始化該函數可以根據具體情況儲存和初始化需要的內容 { for(int i = 0; i < MAX_SIZE; i++) { pa[i] = i; } } int findset(int a)//不帶路勁壓縮 { while(pa[a] != a) { a = pa[a]; } return a; } void union_nodes(int a, int b) //集合合并 { int a1 = findset(a); int b1 = findset(b);if(a1 != b1)//這個判定條件可選,主要是為了防止findset路徑壓縮的時候出現死迴圈{pa[a1] = b1;//如果存的是有向圖,並且做題時集合中元素的順序很重要,不能忽略,那麼這裡應該用"pa[a] = b;" }}
帶路徑壓縮的的findset函數:
1.while版本
int findset(int v) //找元素所在集合的代表元(因為用了路徑壓縮,路徑壓縮的主要目的是為了儘快的確定元素所在的集合) { int t1,t2=v; while(v!=pa[v]) v=pa[v]; while(t2!=pa[t2]) //這裡最佳化的思路還是路徑壓縮(進一步的在尋找函數執行的過程中壓縮路徑),很神奇! { t1=pa[t2]; pa[t2]=v; t2=t1; } return v; }
2.遞迴版本
int findset(int x) { if(pa[x] != x) { int root = findset(pa[x]); return pa[x] = root; } else { return x; } }
個人經驗表明在遞迴版本不棧溢出的情況下,遞迴版本和迴圈版本的效率並沒有太大差別,並且對於帶路徑壓縮的並查集,基本上不會發生“遞迴棧溢出”。
另外,union_nodes函數還可以可以採用啟發學習法合并,思路就是把深度較小的那棵子樹併到深度較大的那棵子樹上,不過一般情況下路徑壓縮就夠用了。
聽人說並查集還可以“離散化”,個人從字面上理解應該是指用map、hash來儲存每個節點,從而當節點分布比較稀疏的時候,可以比普通並查集更快的完成初始化等工作(待商榷)。