1. 程式人生 > >小談並查集及其演算法實現

小談並查集及其演算法實現

並查集

一、演算法介紹:

並查集(Union-find Sets)是一種非常精巧而實用的資料結構,它主要用於處理一些不相交集合的合併問題。

並查集的基本操作有兩個:

1:合併

union(x, y):把元素 x 和元素 y 所在的集合合併,要求 x 和 y 所在的集合不相交,如果兩個集合相交則不合並。

2:查詢

find(x):找到元素 x 所在的集合的代表,該操作常用於判斷兩個元素是否位於同一個集合,只要將它們各自的代表比較一下就可以了。

並查集類似一個森林,是用樹形結構來實現的,所以以下的講解用樹形結構來構造模型:

事先宣告:

(1)一個集合對應一棵樹。

(2)一個集合中的元素對應一個節點。

二、演算法實現:

一、初始化:

我們用n個節點表示n個元素,有一點要特別注意:一個節點,若它的父節點等於它本身,則說明這個節點是根節點。

定義陣列 per[], per[x]代表x的父節點。初始化時我們把per[x] = x,相當於每個節點都是獨立的根節點(每個根節點都代表一個集合)

//n 代表一共有n個元素(n個節點)
for(int i = 1; i <= n; ++i){
	per[i] = i;
}

二、查詢:

find(x),查詢元素x所在的集合,即查詢節點x在哪一棵樹上,這裡我們知道,在一棵樹中,根節點是唯一的,父節點子節點都是相對而言的。要想確定節點x在哪一課樹,我們只需要找到x的根節點就可以了。

如果判斷節點x 和 節點y在不在同一棵樹上,我們只需要找到x 和 y的根節點,若x和y的根節點相同,則x,y在同一顆樹上,否則在不用的樹上。

程式碼:

int find(int x){    
    int r = x;    
    //父節點等於自身的節點才是根節點,    
    //若 r 節點的父節點不是根節點,一直向上找。    
    if(r != per[x])
        r = per[x];
    return r;
}

 

1.1

如圖1.1所示:

我們令節點1為根節點。查詢節點4在哪一棵樹,我們只要找到4的根節點就可以了。

查詢過程: 找到4的父節點per[4]為2,不等於它本身,繼續向上查詢, 2它的父節點per[2]為1,不等於它本身,繼續向上查詢,1的父節點per[1]為1等於它本身,說明1是根節點。

這裡有一個路徑壓縮的優化, 當我們查詢到4的根節點為1時,我們直接將per[4] = 1,即直接把4連在根節點1上,而且在查詢4時還會找到2,可能還有其他的節點,將這些節點的per[]通通都設定為1,這樣下次再查詢4的子節點所在的樹時,查詢次數就縮短了1.

這裡壓縮路徑有兩種寫法,一種是遞迴的,一種是非遞迴的。

1> 遞迴:

int find(int x){
    if(x == per[x])
        return x;
    return per[x] = find(per[x]);
}

2>非遞迴

int find(int x){    
    int r = x;    
    if(r != per[x])        
        r = per[x];    
    int i = x, j;    
    while(i != r){        
        j = per[i];        
        per[i] = r;        
        i = j;    
    }    
    return r;
}

請讀者自己模擬一下這兩種壓縮路徑的方式有何不同。

三、合併:

合併x 和 y所在的樹, 只需要把其中一個樹的根節點設定為令一個樹根節點的子節點即可

void union(int x, int y){    
    int fx = find(x);//x的根節點為fx    
    int fy = find(y);//y的根節點為fy    
    if(fx != fy)        
        per[fx] = fy;
}

但是這裡有一個問題, 是把 x的根節點設定為 y根節點的子節點,還是把y的根節點設定為x根節點的子節點。

節點1和節點2是一棵樹,根節點為1, 節點3是一棵樹,根節點是自身為3.

 

圖1.2

 1.3

如圖1.2所示:

現在我們根節點3作為根節點1的子節點,此時查詢2的根節點,只需要查詢一次。

如圖1,3所示 

現在我們根節點1作為根節點3的子節點,此時查詢2的根節點,需要先找到1,再找到3,多了一次查詢。

所以這裡存在一種優化。我們可以設定一個數組rank[ ],用它來記錄每一棵樹的深度,合併時如果兩棵樹的深度不用,那麼從深度(rank)小的向深度(rank)達的連邊。(但注意,壓縮路徑時會使樹的深度發生變化,但我們不修改rank 的值)

int per[maxn];//記錄父節點
int rank[maxn];//記錄樹的深度
void init(){//初始化n個節點    
    for(int i = 1; i <= n; ++i){        
        per[i] = i;        
        rank[i] = 0;
    }
}
//找到根節點,壓縮路徑
int find(int x){    
    if(x == per[x])        
        return x;    
    return per[x] = find(per[x]);
}

void union (int a, int b){  
    int fa = find(a);  
    int fb = find(b);  
    if(fb != fa){  
        if(rank[fa]  < rank[fb]){  
            per[fa] = fb;  
        }  
        else{  
            per[fb] = fa;  
            if(rank[fa] == rank[fb]) rank[fa]++;  
        }  
    }    
}  

三、基礎例題解析:

題目大意:給出n個城市, m條無向路,問最少再修幾條路使所有城鎮都連通。

最基礎的並查集問題,遞迴壓縮路徑,沒有深度優化

AC程式碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define maxn 1100
#define INF 0x3f3f3f3f
using namespace std;

int per[1100];
int n, m;
//初始化節點
void init(){
    for(int i = 1; i <= n; ++i)
        per[i] = i;
}
//查詢根節點,遞迴壓縮路徑
int find (int x){
    if(x == per[x])
        return x;
    return per[x] = find(per[x]);
}
//合併根節點
void join(int x, int y){
    int fx = find(x);
    int fy = find(y);
    if(fx != fy)
        per[fx] = fy;
}

int main (){
    while(scanf("%d", &n),n){
        scanf("%d", &m);
        init();
        int a, b;
        while(m--){
            scanf("%d%d", &a, &b);
            join(a,b);
        }
        int ans = 0;
        //判斷圖中有幾棵樹,只需要判斷有幾個根節點即可
        //判斷方法;父節點等於本身的節點就是根節點
        for(int i = 1; i <= n; ++i){
            if(per[i] == i)
                ans++;
        }
        //把這些根節點連通,最小需要ans - 1條邊
        printf("%d\n", ans - 1);
    }
    return 0;
}

考察點:判斷是否成環,判斷圖中樹的個數

AC程式碼:非遞迴壓縮路徑,有深度優化

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define maxn 100000 + 100

int per[maxn];
int vis[maxn];
int flag;
int ran[maxn];

//查詢根節點,非遞迴壓縮路徑
int find(int x) {
    int r = x;
    while(r != per[r])
        r = per[r];
    int i,j;
    i = x;
    while(i != r){
        j = per[i];
        per[i] = r;
        i = j;
    }
    return r;
}
//合併根節點,深度優化
void jion (int a, int b){
    int fa = find(a);
    int fb = find(b);
    if(fb != fa){
        if(ran[fa]  < ran[fb]){
            per[fa] = fb;
        }
        else{
            per[fb] = fa;
            if(ran[fa] == ran[fb]) ran[fa]++;
        }
    }
    else
        flag = 0;//判斷是否成環
}

int main (){
    int a, b;
    while(scanf("%d%d", &a, &b) != EOF){
        if(a == -1 && b == -1)
            break;
        if(a == 0 && b == 0){
            printf("Yes\n");
            continue;
        }
        for(int i = 1; i <= 100000; ++i){
            per[i] = i;
            vis[i] = 0;
            ran[i] = 0;
        }
        vis[a] = 1, vis[b] = 1;
        flag = 1;
        jion(a, b);
        while(scanf("%d%d", &a, &b), a || b){
            vis[a] = 1;//並不是所有的房間都用到了,所以需要標記一下
            vis[b] = 1;
            jion(a, b);
        }
        int ans = 0;
        for(int i = 1; i <= 100000; ++i){
            if(per[i] == i && vis[i])
                ans++;
            if(ans > 1){
                flag = 0;
                break;
            }
        }
        if(flag) printf("Yes\n");
        else printf("No\n");

    }
    return 0;
}

四、例題推薦:

HDU 4496--D-City 【並查集 && 刪邊】    

解析:HDU 4496
HDU 1598--find the most comfortable road【並查集 + 列舉】

HDU 2473--Junk-Mail Filter 【並查集 && 刪點】


HDU 3635--Dragon Balls【並查集】

 本人菜鳥一個,如有不對的地方希望各位大神糾正。有關帶權並查集的問題會在日後更新

相關推薦

及其演算法實現

並查集 一、演算法介紹: 並查集(Union-find Sets)是一種非常精巧而實用的資料結構,它主要用於處理一些不相交集合的合併問題。 並查集的基本操作有兩個: 1:合併 union(x,

演算法 C語言實現3

標頭檔案 UnionFind3.h #ifndef UNIONFIND3_H_INCLUDED #define UNIONFIND3_H_INCLUDED #include "stdlib.h" #include "ASSERT.h" typedef stru

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

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

本文將介紹並查集的模板以及各類問題中的應用 並查集,在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查詢一個元素在哪個集合中。這一類問題近幾年來反覆出現在資訊學的國際國內賽題中,其特點是看似並不複雜,但資料量

的樹形實現(C++)(轉載)

摘要:本文介紹了通用並查集的樹形實現,通過壓縮路徑和維持數的平衡,可以保證 查詢和合並的平均時間複雜度為O(1)! 關鍵字:並查集,UnionFind,樹形 並查集基本知識參見博文《並查集的陣列實現》。在並查集的樹形實現中,使用樹狀結構來組織一個 集合,多個集合之

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

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

(C++實現

#include <iostream>   using namespace std;   const int MAX_N = 200001;   int par[MAX_N]; int rank_[MAX_N];   void init(int n){ // 初始

(路徑壓縮算法)

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

2019.9.17 初級資料結構——及其應用

一、並查集基礎 (一)引入 我們先來看一個問題。 某學校有N個學生,形成M個俱樂部。每個俱樂部裡的學生有著一定相似的興趣愛好,形成一個朋友圈。一個學生可以同時屬於若干個不同的俱樂部。根據“我的朋友的朋友也是我的朋友”這個推論可以得出,如果A和B是朋友,且B和C是朋友,則A和C也是

運用與最實現Kruskal演算法

前言 Kruskal是在一個圖(圖論)中生成最小生成樹的演算法之一。(另外還有Prim演算法,之後會涉及到)這就牽扯到了最小生成樹的概念,其實就是總權值最小的一個連通無迴路的子圖。(結合下文的示意圖不難理解)這裡的程式碼並沒有用圖的儲存結構(如:矩陣,鄰接連結

演算法》第四版algs4:union-findC++實現

github地址:https://github.com/Nwpuer/algs4-in-cpp QuickFindUF實現(在檔案"quick_find_uf"中) #pragma once #include <vector> #include <string>

最小生成樹-kruskal演算法(非實現&優先佇列的sh xian&實現

kruskal演算法:構造一個只含n個頂點,而邊集為空的子圖,若將該子圖中各個頂點看成是各棵樹的根節點,則它是一個含有n棵樹的森林 。之後,從網的邊集中選取一條權值最小的邊,若該邊的兩個頂點分屬不同的樹 ,則將其加入子圖,也就是這兩個頂點分別所在的 兩棵樹合成一棵樹;反之,若該邊的兩個頂點已落在同一

Agri-Net的Kruskal演算法+實現(按大小合併+路徑壓縮)

Agri-Net的Kruskal演算法+並查集實現 演算法複雜度分析     對所有的邊進行排序,排序複雜度為O(mlogm),隨後對邊進行合併,合併使用並查集,並查集使用link by size的方式實現,同時在find函式實現了路徑壓縮。每

使用UnionFind和優先佇列PriorityQueue實現Kruskal演算法

拿到題目,先看看UnionFind 和 PriorityQueue 怎麼實現吧,誰讓上課沒好好聽呢。 Kruskal演算法是通過按照權值遞增的順序依次選擇圖中的邊,當邊不處於同一連通分量時加入生成樹,

演算法入門---java語言實現(Union-Find)小結

圖片來自慕課網,僅僅為了記錄學習。 基本概念 /** * * 並查集,用來解決連通問題的,兩個節點之間是否是連通的。 * 此處的節點是抽象的概念:比如使用者和使用者之間,港口和港口之間。

的簡介及其C/C++程式碼的實現(某公司招聘筆試試題)

       當年, 我在一個公司實習, 某次, 在一次演算法交流的過程中, 我第一次聽到了並查集這個看似高大上的概念, 也再一次感覺到了自己的無知。         對於一個非計算機專業的人來說, 你跟他說並查集, 就有點像你對著計算機專業的人說Gibbs現象或者FFT一

和Union-Find演算法及其改進

並查集 定義        並查集(Union-Find Sets)也叫不相交集合(Disjoint Sets),是一種用於維護若干個不相交元素所構成的集合的一種樹型資料結構。並查集常用來處理一些不相交元素的合併與查詢問題,在使用中常常用森林來表示。 主要操作  

的使用及其實現

並查集 概述 詳細教程參考之前轉載的並查集詳解 性質 並查集演算法(union_find sets)不支援分割一個集合,求連通子圖、求最小生成樹 用法 並查集是由一個數組pre[],和兩個函式構成的,一個函式為find()函式,用於尋

DS-7.1實現求最小生成樹的克魯斯卡爾演算法(實現)

寫在前面:首先吐槽一下學校的OJ,題目上說是輸入字母結果後臺測試樣例卻出現數字= =!害得我改來改去提交了7次都是錯的。(呸 回到正題 題目描述 已知有權無向圖G,利用克魯斯卡爾演算法求出該圖的最小生成樹。 輸入 第一行輸入兩個正整數n和m(空格間隔)

克魯斯卡演算法 實現最小生成樹(虛擬碼)

克魯斯卡演算法思想:        在無向圖中按照邊的權值從小到大排序,然後從最小邊開始掃描,並且檢測當前邊是否為候選變,即是否該邊會構成迴路,如不構成,則將該邊併入當前的生成樹 typedef st