1. 程式人生 > >POJ2524 並差集以及優化(路徑壓縮+按秩合併)的簡潔介紹

POJ2524 並差集以及優化(路徑壓縮+按秩合併)的簡潔介紹

0 題意很簡單,略。

1

(下面的描述中,所提及的集合的深度和集合的寬度是指:

以1,3,4都指向2為例:
    2   
 /  |  \
1   3   4
樹的深度為2(兩層),樹的寬度為3(1 3 4 三個)


①關於按秩合併中秩的一點說明,我理解的秩是集合的深度,大部分部落格的程式碼與我的理解相同(即2-④ Join()中按秩合併程式碼中的秩就是嚴格代表著集合的深度),但是有的部落格上:

 p1=Find(a),p2=Find(b); 
    if(rank[p1]<=rank[p2]){
        parents[p1]=p2;
        rank[p2]+=rank[p1];
    }
    else if(rank[p2]<=rank[p1]){
        parents[p2]=p1;
        rank[p1]+=rank[p2];
    }
兩種寫法都是對的,雖然沒有說明,但是這裡的秩嚴格來說已經不代表深度,不過卻同樣起到了根據深度來進行合併的控制作用,不同的是多加了一個規則:在深度相同時,寬度更大的舊集合的代表優先成為新集合的代表,這一點對於路徑壓縮的優化作用不大。因為路徑壓縮的效率與集合的深度有關,與寬度無關。如果沒有充分理解或許會對兩種寫法感到疑惑,特此說明,如有不同理解,歡迎指正。

②路徑壓縮和按秩合併的舉例理解:

1)路徑壓縮(對Find()優化,只需參考2①,2②、2③中的Find()部分)

//路徑壓縮前,每次查詢最低端的元素的最高負責人(這個集合的代表),都要不斷往上查對它負責的元素,直到查到最後那個只需要對自己負責的元素即為這一連串元素的最高負責人。
1->2, Find(1)=Find(2)=2
1->2->3, Find(1)=Find(2)=Find(3)=3;
1->2->3->4, Find(1)=Find(2)=Find(3)=Find(4);

//路徑壓縮後,每次查詢最低端的元素的代表完畢之後,都順便將該元素以及該元素上面的所有元素的負責人都設定為直接對最高負責人負責,就方便了再次查詢。
1->2, Find(1)=Find(2)=2
1->2->3 ---- 1->3,2->3, Find(1)=3,Find(2)=3,Find(3)=3
1->2->3->4 ---- 1->4,2->4,3->4, Find(1)=4,Find(2)=4,Find(3)=4,Find(4)=4;

2)按秩合併(對Join()優化,只需參考2①,2④中的Join()部分)

//按秩合併前,有可能出現一個長串,那麼路徑壓縮時,需要改變的值就很多
Join(1,2)  1->2
Join(2,3)  1->2->3
Join(3,4)  1->2->3->4
Find(1)    1->4,2->4,3->4 //(路徑壓縮時需要改變三個值,即與被併入集合的深度有關)

//按秩合併後,總是儘可能使合併後的新集合的深度不會繼續加深,那麼路徑壓縮時,改變的值就相對較少
 Join(1, 2)
   2   3   4 
  /
 1
 
 Join(2, 3)
   2    4
 /   \
1     3

 Join(3, 4)
    2   
 /  |  \
1   3   4

Find(1)=2, 1->2 //(路徑壓縮時相對改變較少的值)

2關於POJ2524的AC CODE

①樸素程式碼

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
///樸素程式碼(沒有路徑壓縮,也沒有按秩合併)
using namespace std;
int n,m;
int kase=0;
int parents[50010];
int cnt;//記錄最後答案,每次Join成功,就自減減一次,最後得到的即一共有多少個集合(當然也可以最後走一個for,有一個數的root是指向自己的就自加加一次,那麼得到的代表個數自然就是集合個數)
int Find(int x){
    int root=x;
    while(root!=parents[root]){
        root=parents[root];//找到x的真正根節點,即最後的root
    }
    return root;
}
void Join(int a,int b){
    int p1=Find(a);
    int p2=Find(b);
    if(p1==p2){
        return ;
    }
    parents[p1]=p2;//不按秩合併,那麼新的集合的代表由原來的兩個舊集合中哪個舊集合的代表來充當就不重要了,於是隨便指定了一個
    cnt--;
    return ;
}
int main()
{
    while(~scanf("%d%d",&n,&m)&&n!=0){
        cnt=n;
        for(int i=1;i<=n;i++){
            parents[i]=i;
        }
        int a;
        int b;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&a,&b);
            Join(a,b);
        }

        cout<<"Case "<<++kase<<": "<<cnt<<endl;
    }

    return 0;
}

②遞迴的路徑壓縮

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
///遞迴的路徑壓縮(對Find()的優化)
using namespace std;
int n,m;
int kase=0;
int cnt;
int parents[50010];
int Find(int x){
    int root=x;
    if(root!=parents[root])
        root=Find(parents[root]);//找到x的真正根節點,即最後的root<span style="font-family: Arial, Helvetica, sans-serif;">,同時在遞歸回溯時,將中間經過的元素的直接負責人都設定為最高負責人,方便下一次查詢</span>
    return root;
}
void Join(int a,int b){
    int p1=Find(a);
    int p2=Find(b);
    if(p1==p2){
        return ;
    }

    parents[p1]=p2;//不按秩合併,那麼新的集合的代表由原來的兩個舊集合中哪個舊集合的代表來充當就不重要了,於是隨便指定了一個
    cnt--;
    return ;
}
int main()
{
    while(~scanf("%d%d",&n,&m)&&n!=0){
        cnt=n;
        for(int i=1;i<=n;i++){
            parents[i]=i;
        }

        int a;
        int b;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&a,&b);
            Join(a,b);
        }

        cout<<"Case "<<++kase<<": "<<cnt<<endl;
    }

    return 0;
}


③非遞迴路徑壓縮

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
///非遞迴(防止棧溢位)路徑壓縮(對Find()的優化)
using namespace std;
int n,m;
int kase=0;
int cnt;
int parents[50010];
int Find(int x){
    int root=x;
    while(root!=parents[root]){
        root=parents[root];//找到x的真正根節點,即最後的root
    }
    int temp2;
    while(x!=parents[x]){
        temp2=parents[x];//重新尋找一遍,並將途中所有節點都指向根節點,即壓縮路徑,不用遞迴防止棧溢位
        parents[x]=root;
        x=temp2;
    }
    return root;
}
void Join(int a,int b){
    int p1=Find(a);
    int p2=Find(b);
    if(p1==p2){
        return ;
    }

    parents[p1]=p2;//不按秩合併,那麼新的集合的代表由原來的兩個舊集合中哪個舊集合的代表來充當就不重要了,於是隨便指定了一個
    cnt--;
    return ;
}
int main()
{
    while(~scanf("%d%d",&n,&m)&&n!=0){
        cnt=n;
        for(int i=1;i<=n;i++){
            parents[i]=i;
        }

        int a;
        int b;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&a,&b);
            Join(a,b);
        }

        cout<<"Case "<<++kase<<": "<<cnt<<endl;
    }

    return 0;
}

④非遞迴路徑壓縮+按秩合併

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
///非遞迴(防止棧溢位)路徑壓縮(對Find()的優化)+按秩合併(對Join()的優化)
///(先理解路徑壓縮然後就容易理解按軼合併的必要性,正是因為按軼(深度)合併的存在,使得合併之後儘量增加樹的寬度而不增加深度(當兩個集合的深度相同時合併後形成的新集合是原來的集合的深度+1),從而使路徑壓縮儘可能壓縮的路徑層數變少)
using namespace std;
int n,m;
int kase=0;
int cnt;
int parents[50010];
int rank[50010];//以此為代表的集合的深度,用於合併時的比較
int Find(int x){
    int root=x;
    while(root!=parents[root]){
        root=parents[root];//找到x的真正根節點,即最後的root
    }
    int temp2;
    while(x!=parents[x]){
        temp2=parents[x];//重新尋找一遍,並將途中所有節點都指向根節點,即壓縮路徑
        parents[x]=root;
        x=temp2;
    }
    return root;
}
void Join(int a,int b){
    int p1=Find(a);
    int p2=Find(b);
    if(p1==p2){
        return ;
    }
    
    cnt--;
    if(rank[p1]<rank[p2]){
        parents[p1]=p2;
    }
    else if(rank[p2]<rank[p1]){
        parents[p2]=p1;
    }
    else if(rank[p1]==rank[p2]){
        parents[p1]=p2;//兩個集合的秩(深度)相同時,新的代表是哪個就不重要了(因為路徑壓縮的效率只與深度有關,所以相等深度的兩個舊集合哪個寬度更大並不重要)
        rank[p2]++;//新的集合的秩(深度)要+1;
    }
    return ;
}
int main()
{
    while(~scanf("%d%d",&n,&m)&&n!=0){
        cnt=n;
        for(int i=1;i<=n;i++){
            parents[i]=i;
            rank[i]=0;
        }

        int a;
        int b;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&a,&b);
            Join(a,b);
        }

        cout<<"Case "<<++kase<<": "<<cnt<<endl;
    }

    return 0;
}


理解不同之處,歡迎指正。

相關推薦

POJ2524 以及優化路徑壓縮+合併簡潔介紹

0 題意很簡單,略。 1 (下面的描述中,所提及的集合的深度和集合的寬度是指: 以1,3,4都指向2為例: 2 / | \ 1 3 4 樹的深度為2(兩層),樹的寬度為3(1 3 4 三個) ) ①關於按秩合併中秩的一點說明,我理解的秩是集合

的“優化leader合併和“查優化路徑壓縮

       在博文http://blog.csdn.net/stpeace/article/details/46506861中, 我們已經詳細地瞭解了並查集, 不過, 那個程式略顯粗糙, 下面我們考

Bond-合併

題意:給出一張n個點m條邊的無向圖, 每條邊有一個危險度,有q個詢問, 每次給出兩個點s、t,找一條路, 使得路徑上的最大危險度最小。 思路:首先,我們可以發現,如果求一個最小生成樹, 那麼任意兩點, 在生成樹上有唯一路徑, 而且這條路徑上的最大危險值一定最小。 但是n和

合併

並查集-按秩合併 題目:UVA-11354 題目大意:給出一張n個點m條邊的無向圖, 每條邊有一個危險度,有q個詢問, 每次給出兩個點s、t,找一條路, 使得路徑上的最大危險度最小。 思路:首先,我們可以發現,如果求一個最小生成樹, 那麼任意兩點, 在生成

How many Tables應用

題目描述: Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You

路徑壓縮,基礎uva1329 合作網路

【問題描述】   有n個結點(編號為1..n),初始時每個結點的父親都不存在。你的任務是執行一次I操作和E操作,格式如下:   I u v:把節點u的父親點設定為v,距離為|u-v|除以1000的餘數。輸入保證執行指令前u沒有父親節點。   E u:詢問u 到根接點的距

【BZOJ1015】【JSOI2008】星球大戰Starwar離線

傳送門 一道簡單題 所謂正難則反 我們考慮離線從後往前操作 就變成了每次加一個點求當前聯通塊個數 並查集就完了唄 程式碼稍微寫的有些繁瑣 但肯定還是可以看的 #include<bits/stdc++.h> using namespace std;

合併路徑壓縮

演算法分類: 資料結構 演算法原理: 通過find函式找出該節點的根節點, 通過UNION函式將兩棵樹合併。 加入rank[N]來記錄每個節點的秩(即樹的高度),並按秩進行合併,可避免合併時的最糟糕情況,(樹形為一條直線) 通過路徑壓縮可以減少每次尋找根節點的次數。 演

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

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

——最小連線路徑和Kruskalhdu1301

好,首先說一下並查集的標準定義: 概述: 在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查詢一個元素在哪個集合中。這一類問題近幾年來反覆出現在資訊學的國際國內賽題中,其特點是

總結路徑壓縮+啟發式合併

並查集一、並查集是處理什麼問題的:並查集,是一種用來管理元素分組情況的資料結構,可以處理一些不相交集合的合併與查詢問題;它可以進行合併操作,但不能進行分割操作。二、兩大操作:(1)查詢元素a和元素b是否屬於同一集合;(2)合併元素a和元素b所在的集合;三、主要的步驟:初始化:

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

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

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

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

路徑壓縮 && 啟發式合併!!!

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> usin

淺談路徑壓縮算法

nbsp bsp 節點 oid int 數組存儲 父親節 urn 初始化 並查集的存儲:用法fa[ ]數組存儲並查集。 並查集的初始化:另fa[i]=i. 並查集的get()操作: int get(x){   if(x==fa[x])   {     retur

BZOJ 3674 可持久化加強版主席樹變形

als ret desc scan sync scanf ops 只需要 ica 3673: 可持久化並查集 by zky Time Limit: 5 Sec Memory Limit: 128 MB Submit: 2515 Solved: 1107 [

頁面重繪和回流以及優化

圖片大小 處理流 create 意圖 borde 基本上 nal arch 似的 源文章地址:http://www.css88.com/archives/4996 在討論頁面重繪、回流之前。需要對頁面的呈現流程有些了解,頁面是怎麽把html結合css等顯示到瀏覽器上的,下面

[帶權] Jzoj P1503 體育場

現在 png pan namespace str main find mat fix Description   觀眾席每一行構成一個圓形,每個圓形由300個座位組成,對300個座位按照順時針編號1到300,且可以認為有無數多行。現在比賽的組織者希望觀眾進入場地的順序

[][lca][dfs] Jzoj P5798 樹

100% dset 此外 out oge 表示 output 技術 code Description 我們有一顆從1到n編號的n個結點的樹,此外,您將從樹中獲得M個節點對,形式為(a1,b1),(a2,b2),…(am,bm).我們需要給每一條邊定向,使

【LeetCode】 union-find共16題

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica } 【128】Longest Consecutive Sequence  【130】Surrounded Regions  【200】Number of Is