並查集:(union-findsets)
一種簡單的用途廣泛的集合. 並查集是若干個不相交集合,能夠實現較快的合併和判斷元素所在集合的操作,應用很多,如其求無向圖的連通分量個數等。最完美的應用當屬:實現kruskar演算法求最小生成樹。
並查集的精髓(即它的三種操作,結合實現**模板進行理解):
1、make_set(x) 把每乙個元素初始化為乙個集合
初始化後每乙個元素的父親節點是它本身,每乙個元素的祖先節點也是它本身(也可以根據情況而變)。
/* 初始化集合*/
void make_set(int x)
2、find_set(x) 查詢乙個元素所在的集合
查詢乙個元素所在的集合,其精髓是找到這個元素所在集合的祖先!這個才是並查集判斷和合併的最終依據。
判斷兩個元素是否屬於同一集合,只要看他們所在集合的祖先是否相同即可。
合併兩個集合,也是使乙個集合的祖先成為另乙個集合的祖先。
優化一:
使用並查集查詢時,如果查詢次數很多,那麼使用樸素版的查詢方式肯定要超時。比如,有一百萬個元素,每次都從第一百萬個開始找,這樣一次運算就是10^6,如果程式要求查詢個一千萬次,這樣下來就是10^13,肯定要出問題的。
這是樸素查詢的**,適合資料量不大的情況:
int findx(int x)
下面是採用路徑壓縮的方法查詢元素:所謂路徑壓縮,就是查詢的同時,順路把該經過的元素都直接指向父節點,方便下次查詢!這樣以後再次查詢時複雜度就變為o(1)
int find(int x) //查詢x元素所在的集合,回溯時壓縮路徑
//從x結點搜尋到祖先結點所經過的結點都指向該祖先結點
return parent[x];
}
上面是一採用遞迴的方式壓縮路徑, 但是,遞迴壓縮路徑可能會造成溢位棧,我曾經因為這個re了n次,下面我們說一下非遞迴方式進行的路徑壓縮:
int find(int x)
return r; //返回根節點的值
}
3、union(x,y) 合併x,y所在的兩個集合
合併兩個不相交集合操作很簡單:
利用find_set找到其中兩個集合的祖先,將乙個集合的祖先指向另乙個集合的祖先。如圖
如圖.每個兒子都指向最頂的祖先,這樣查詢就變為o(1)
優化二:
union(x,y)時 按秩(高度)合併
即合併的時候將高度少的集合合併到元素多的集合中,這樣合併之後樹的高度會相對較小。
void union(int x, int y)
else
father[x] = y;}}
送上幾道poj好題:
部分**
路徑壓縮 並查集路徑壓縮
如何描述乙個複雜的連線關係?如圖,很容易判斷緊鄰的2個人關係,但中間的連線很多很亂,怎麼判斷出兩個人的關係呢?並查集就是一種結構,通過儲存節點以及節點上的標籤,來判斷這兩個節點是否連線在一起。當兩個節點繫結時,可以任選其中乙個節點的標籤,指定另乙個節點。當判斷兩個節點是不是連線時,可以上溯節點的祖宗...
並查集路徑壓縮
使用並查集查詢時,如果查詢次數很多,那麼使用樸素版的查詢方式肯定要超時。比如,有一百萬個元素,每次都從第一百萬個開始找,這樣一次運算就是10 6,如果程式要求查詢個一千萬次,這樣下來就是10 13,肯定要出問題的。這是樸素查詢的 適合資料量不大的情況 int findx int x 下面是採用路徑壓...
並查集壓縮路徑
並查集壓縮路徑 先找到所有節點的最終的根節點,然後通過將所有不與根節點直接相連的並且父節點是根節點的節點直接接在根節點上面,從而減少查詢時間,來達到時間節省的目的!關於並查集尋找父節點的演算法 主要分為兩類 一 通過遞迴求解。優點 較短,並且遞迴的過程中已經包含了壓縮路徑。缺點 資料較大的時候,容易...