1. 程式人生 > >基於Rank的並查集優化

基於Rank的並查集優化

在上一小節中,我們討論了基於Size的並查集優化方法,即在合併兩個集合時,通過判斷兩個集合元素的數量大小來決定把哪一個集合併入另一個集合當中,從而減少了因為合併集合使得合併後樹的層數增多的情況,因此執行find操作所需的步驟數量也大大減少了。但是,沒有絕對完美的優化方法,這種基於Size的合併策略在有的時候卻並不能很好的解決合併時發生層數增多的問題,例如下面這種情況:


我們可以看到,如果我們現在需要把4和2兩個元素合併在一起,即把他們各自的集合合併為同一個集合,2的根元素為7,4的根元素為8,且7為根的集合的樹所含有的元素個數為6個,而以8為根的集合所含有的元素個數為3個,根據Size的策略,我們會把8節點的父親指標指向7根節點來合併這兩個集合。如下圖所示:


然而,經過這樣歸併後,這棵樹的層數就由原來的兩層變成了四層,如果我們換一個方向去合併這兩個集合,把7根節點的父親指標指向8根節點的話來合併這兩個集合,樹形就如下圖所示:

我們發現,原來以8作為根節點的樹的層數為3層,現在合併後的集合的層數還是3層,這樣合併後的集合的層數也要比上面的4層少一層,因此我們可以得出這樣的一個結論:僅僅依靠集合的Size來判斷由誰指向誰,並不是完全準確的,更準確的是比較集合的層數來判斷誰指向誰,這樣最後合併出來的集合的層數能夠竟可能的壓縮至最小,因此執行find操作的效率將大大提高。在集合中,層數越少,對於每一個節點平均來說,找到根節點所需要查詢的次數就會越小。

因此,我們可以用一個rank陣列來替換原來的Size陣列,Rank[i]表示以 i 為根節點的集合的層數,即樹的高度。下面我們來看一下具體的實現程式碼:

並查集的基礎結構:

 private:
        int* parent;//parent指標指向一個專門用來記錄元素父親元素的指標
        int count;//記錄集合中元素的數量
        int *rank;//rank[i]表示以i為根的集合的層數
建構函式:
 UnionFind4(int n){
            parent=new int[n];//初始化parent陣列
            rank=new int[n];//rank[i]表示以i為根元素的集合所表示的樹的層數
            count=n;
            /*
             * 切記一定要記得初始化parent陣列
             * 使每個元素的parent指標都指向自己
             */
            for(int i=0;i<n;i++){
                parent[i]=i;
                rank[i]=1;//最開始所有的元素都指向自己,每一個元素都是根元素,每一個集合都只有一層
            }

        }
接下來是最重要的連線操作:

對於兩個集合來說,如果兩個集合的層數不一樣,我們只需要把層數小的集合的根元素的父親節點指向另一個集合的根元素就好了,而且最後合併出來的集合的層數是不變的。還是以下圖為例:


根據rank的策略,根節點7元素的父親節點指向了8元素,然而合併後的集合的層數原來為3層(8,3,4集合),7節點的集合與其合併後,合併後的集合的層數仍然為3層,是不會變的。

合併後集合層數唯一會變的情況,就是兩個集合的層數一模一樣時。我們可以這樣去理解,假設兩個集合都只有一個元素,那麼這兩個集合的層數都為一層,層數相同時,此時誰的根節點的父親節點指向另一個根節點都無所謂了,但是這樣合併後的集合層數要比原來多了一層。(原來兩個集合都為一層,合併後的集合就變成兩層了),因此,Unionelements的程式碼具體如下:

  //合併兩個元素所在的集合
        void unoinelements(int p,int q){
            int proot=find(p);//找出p元素位於的集合的根元素
            int qroot=find(q);//找出q元素位於的結合的根元素
            if(proot==qroot){//如果兩個元素的根元素都為同一個元素,則它們已經在同一個集合當中了
                return;
            }
            else{//兩個元素在不同的集合當中
                if(rank[proot]<rank[qroot]) {//p元素所在的集合的層數小於q元素所在的集合的層數
                    parent[proot] = qroot;//p集合的根節點父親指標指向q集合的根節點
                }
                else if(rank[proot]>rank[qroot]){//此時p集合的層數要大於q集合的層數
                    parent[qroot]=proot;//q集合的根元素的父親節點指向p集合的根元素
                }
                else{//p集合與q集合的層數相同
                    parent[proot]=qroot;//二者根元素任意連線都可,這裡預設把p集合的根元素父親節點指向q集合的根節點
                    rank[qroot]++;//q集合的層數此時會增加一層
                }
            }
        }

我們接下來在檢驗一下基於Rank優化的並查集的效率如何,同樣是進行200萬次的操作:
int main() {
        int n=1000000;
        UnionFindTestHelper::TestUF1(n);
        UnionFindTestHelper::TestUF2(n);
        UnionFindTestHelper::TestUF3(n);
        UnionFindTestHelper::TestUF4(n);
    return 0;
}
結果如下:


我們發現,基於Rank的優化策略效率同樣很高,但是有的時候可能會比基於Size優化策略的並查集慢一些,這是因為基於Rank優化的並查集的Unionelements操作中的if else判斷語句更多了,因此要比Size慢一點,但是效率仍然很高,還可以克服一些極端的情況,因此實現並查集使用rank策略就好了。

如需獲取本次版本的所有原始碼,請點選此處移步我的Github程式碼倉庫。

相關推薦

基於Rank優化

在上一小節中,我們討論了基於Size的並查集優化方法,即在合併兩個集合時,通過判斷兩個集合元素的數量大小來決定把哪一個集合併入另一個集合當中,從而減少了因為合併集合使得合併後樹的層數增多的情況,因此執行

HDU 1198 Farm Irrigation (優化,構圖)

++ space int con can 組成 union trac 輸入 本題和HDU暢通project類似。僅僅只是暢通project給出了數的連通關系, 而此題須要自己推斷連通關系,即兩個水管能否夠連接到一起,也是本題的難點所在。 記錄狀態。不斷combine()

POJ 1456 Supermarket(貪心演算法,可用優化)

題意:         有n件商品需要賣,每件商品由(p,t)描述。其中p表示該商品被賣出可獲得的利潤,t表示該商品被賣出的截止時間。時間從1開始計時,每件商品被賣出的話需要佔用1個時間單位。如果某件商品的t=3,那麼該商品最多隻能在時間1,時間2或時間3 這3個時間點上賣

『最小生成樹』Kruskal演算法——加邊法 (優化 + C++語言編寫 + 例題)

『演算法原理』          在一個連通網的所有生成樹中,各邊的代價之和最小的那顆生成樹稱為該連通網的最小代價生成樹(Minimum Cost Spanning Tree),簡稱最小生成樹(MST)。         Kruskal演算法之所以叫加邊法,就是因為其本質

優化連邊

優化 unsigned span type dijkstra ces oid color 二維 很多題目均可用並查集優化連邊, 跳過一些已經訪問過的點 比方說, 對[L,R]範圍進行一定更新可以這樣寫 for (int i=Find(L); i<=R; i=Fi

hdu1811 Rank of Tetris +拓撲排序

namespace str tin i++ 自己 sca col color mem 1 #include <stdio.h> 2 #include <string.h> 3 #include <vector>

HDU 1811 Rank of Tetris(+拓撲排序 非常經典)

memory max scan ble tput col turn 排行榜 行動 Rank of Tetris Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe

Rank of Tetris +拓撲排序★★

題目:https://blog.csdn.net/yao1373446012/article/details/52131283?utm_source=blogxgwz0 AC程式碼: #include<bits/stdc++.h> using namespace std; struc

【HDU 1811】 Rank of Tetris +拓撲

題目連結:傳送門   中文題目就不闡述題意了,我最開始的想法是種類並查集,但是細想一下,發現並不可行,因為題目沒有告訴有多少型別。   做法:關鍵問題是處理等號的兩個點,其實兩個點相等,就相當於這兩個人的排名是一樣的,我們用並查集搞定這麼些個相等排名的點,之後把有

資料結構(十一)的實現和優化

並查集 1. 陣列儲存的基礎實現 此時並查集用一個結構體儲存,data 存值(下標+1),parent 存父結點下標 查詢操作中先線性查詢到需要找到的值,再迴圈查詢其根結點 並操作先找到兩合併陣列的樹根,如果不相等,把一棵樹掛到另一棵樹根下 #include&l

HDU 1811 Rank of Tetris(+拓撲排序)

題意:給出n個點,m個關係。 u > v表示u的rating高於v的。 u < v表示u的rating低於v的。 u = v表示u的rating等於v的,則最後的排序按照標號大小來排。 問給定的關係,如果存在矛盾,輸出CONFLICT,如

的實現與優化

並查集是一種樹型的資料結構,用於處理一些不交集(Disjoint Sets)的合併及查詢問題。有一個聯合-查詢演算法(union-find algorithm)定義了兩個用於此資料結構的操作: Find:確定元素屬於哪一個子集。它可以被用來確定兩個元素是否屬於同一子集。 Union:

及其簡單應用:優化kruskal演算法

並查集是一種可以在較短的時間內進行集合的查詢與合併的樹形資料結構 每次合併只需將兩棵樹的根合併即可 通過路徑壓縮減小每顆樹的深度可以使查詢祖先的速度加快不少 程式碼如下: int getfather(int x) //查詢祖先 { if(f

最小生成樹kruskal演算法(貪心++堆優化

kruskal演算法克魯斯卡爾演算法的基本思想是以邊為主導地位,始終選擇當前可用(所選的邊不能構成迴路)的最小權植邊。所以Kruskal演算法的第一步是給所有的邊按照從小到大的順序排序。這一步可以直接使用庫函式qsort或者sort。接下來從小到大依次考察每一條邊(u,v)。

Rank HDU - 1704(藉由本題說明與傳遞閉包的區別)

更新:突然想明白了,本題不可以用並查集來做。 我原來的思路是,比如通過並查集得到3個集合,每個集合的元素個數分別是7,8,9那麼最終無法判斷的個數就是7*8+7*9+8*9。但是並查集只能判斷他們是否屬於同一個集合,而不能判斷一個集合內部的每兩個元素之間是否有輸贏關係。比如輸入1 2和

--演算法,優化,變種

一、定義並查集是一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。 基礎的並查集能實現以下三個操作:1.建立集合;2.查詢某個元素是否在一給定集合內(或查詢一個元素所在的集合); 3.合併

的兩種優化(按秩合併,路徑壓縮)

並查集是建立在對不相交集合進行的兩種基本操作的基礎之上的。操作之一:檢索某元素屬於哪個集合;操作之二:合併兩個集合。黑書上說了,這種結構顯然可以用連結串列或森林實現,顯然用連結串列進行查詢時間複雜度應該是O(n)級別的,而使用森林進行查訪如果處理的好時間複雜度就應該是O(l

優化---路徑壓縮與啟發式合併

並查集的優化分為兩類:一種是 優化查詢的路徑壓縮,一種是啟發式合併(按集合大小合併與按秩(高)合併) 路徑壓縮 a.描述:如果查詢的總路徑過長,尤其是一條鏈的情況下,那麼樸素的查詢可能會超時。於

基於+Kruskal演算法的matlab程式及最小生成樹繪圖

學了一天最小生成樹,稍稍總結一下,這是第一篇 kruskal演算法 關於kruskal演算法已有大量的資料,不再贅述,演算法流程為: 得到鄰接矩陣和權值; 初始化,連線距離最小的兩點; 連線距離次小的兩點,如果形成迴路則取消連線;重複上述連線步驟,直到所

牛客練習賽39 D 動態連通塊+ X bitset 優化

alt reat lambda stream hid inf view puts tchar https://ac.nowcoder.com/acm/contest/368/D 題意 小T有n個點,每個點可能是黑色的,可能是白色的。小T對這張圖的定義了白連通塊和黑連通塊