演算法週刊 並查集

2022-09-15 00:24:16 字數 2018 閱讀 6117

眾所周知,在qq空間裡能夠看到您好友以及好友的好友所發布的秘密說說,您想匿名發布吐槽但又不希望讓「敏感人」看到,所以每次發布前都需要先查詢您的吐槽是否會被該說說的「敏感人」所看到。善於管理人際關係、精通人性的您已經擁有了您每位好友的列表名單,但由於您是乙個**空調,每次發條說說前都要逐個檢驗浪費了許多時間,如何妥善管理這些資訊使其便於查詢呢?

您曾經從朋友的列表中刪除了重複的q友以減少資料量,保證了每個q友只會出現一次,那麼現在將問題提煉一下,也就是「如何管理若干不相交的集合,使其具備合併集合,以及查詢兩個元素是否在乙個集合中等功能」的問題。

很容易想到,既然不相交,那麼就可以用乙個連續的陣列來儲存元素(下標)與集合(值)的對應關係,在每個集合中任選乙個元素來代表這個集合。每次需要合併兩個集合時,就遍歷整個陣列中找到其中乙個集合的元素存放的位置,將其內容修改為與要合併的集合相同就好了。

不過,這樣每次在合併集合時都需要遍歷整個陣列,如果集合很多而每次只是進行兩兩合併,那麼這樣的方案顯然效率太低。如何優化來提高合併效率?

可以想到,樹在直接合併的時候只要用o(1)的時間「把一棵掛到另一棵上」就可以了,這裡可以使用樹來維護這些集合,把乙個集合的所有元素不按順序地放在同一棵樹裡,合併的時候直接把一棵掛到另一棵上就可以了。而判斷是不是在同乙個集合中只需要通過判斷樹唯一的根結點是否相同即可。

根據這個思路設計乙個類:

class uf

//查:查詢根節點

int find(int n)

//並:合併兩個集合(樹)

bool union(int x,int y)

};

仔細審視下這個方法,發現合併的時間從\(o(n)\)變成了\(o(1)\),但是查詢的時間從\(o(1)\)變成了\(o(h)\),\(h\)為結點的深度(\(1<=h<=n\)),這個時間最大能夠到達\(o(n)\)。而在實際生活中,查詢的需求顯然更加頻繁。所以這樣的做法更好了,但還是不夠好。

考慮優化時應當從結點深度h入手,將h盡量降低到1,讓樹的子孫節點都直接指向根節點,這個操作是可以放在find()函式的執行過程中的。

//改造後的find()

這樣將結點尋找根時所經過結點的路徑進行壓縮,讓樹更加扁平的方法就叫做路徑壓縮。

經過了路徑壓縮一般已經滿足要求了,更進一步還可以「按秩合併」。

按秩合併總的來說就是把規模更小的樹掛在更大的樹的樹根上,有的說法是根據高度,有的是根據結點數量。這裡筆者簡單地理解為未經過路徑壓縮時根據深度按秩合併優化結果很直觀;而經過了路徑壓縮,深度的改變不好記錄,可以選擇根據結點數來合併。樸素地想:被合併的樹除了根節點都不是直接接在合併樹的根節點上的,因此被合併的樹結點數越多,合併後不直接接在根節點上的結點就越多,所以被合併樹的結點數應當盡可能地少。按照以上路徑壓縮+按秩合併的思路最後優化得到:

class uf//初始化森林,初始化結點數

} int find(int n)

bool union(int x,int y)//集

//x樹更大,讓y根指向x根,增加結點數

else//y樹更大,讓x根指向y根,增加結點數

return 0;

}};

int main()

{ int n,m,z,x,y;cin>>n>>m;

uf s(n);

for(int i=0;i>z>>x>>y;

if(z==1)s.union(x,y);

else{

if(s.find(x)==s.find(y))cout<

並查集演算法

所謂並查集,它是乙個集合,這個集合的元素也是集合,他支援三種操作 makeset x 建立乙個只有乙個元素x的集合x0,將這個集合放入並查集中 findset x 在並查集中尋找乙個元素s 注意並查集的元素s也是集合 滿足 x屬於s union x,y 將並查集中的元素s1,s2合併,其中x屬於s1...

並查集演算法

並查集是一種樹型的資料結構,用於處理一些不相交集合 disjoint sets 的合併及查詢問題。常常在使用中以森林來表示。讓每個元素構成乙個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合併。1 makeset s 建立乙個新的並查集,包含s個單元素集合。2 union x,y 把x ...

並查集演算法

includeint pre 10 int find int x 查詢祖先節點 int i x,j while i r 壓縮路徑 return r void join int x,int y int main 2 食物鏈問題 description 動物王國中有三類動物a,b,c,這三類動物的食物鏈...