1. 程式人生 > >並查集應用總結

並查集應用總結

一.並查集的概念

並查集是一種演算法可以用來判斷相互關聯(同屬一個集合)的元素屬於幾個集合,也可以用來判斷圖結構中的兩點是否是聯通的。並查集的設計思路是這樣的:

在程式執行過程中任意元素一定輸於以下三種狀態

1.即f[i]=i,在該種狀態下的元素可能是未被合併(初始狀態),也可能是經過合併但是選擇的父節點就是這個節點

2.已有父節點並且就是當前狀態下真正的父節點(其實是最正常的狀態,第二種狀態也包含第一種情況)。

3.已有父節點但並非當前狀態下真正的父節點,可能是以前的父節點,由merge函式可以看出,當合並兩個節點時只是合併兩個節點的父節點,當這個節點的父節點不是本身的時候這個節點的父節點就不會被更改成新的父節點,這種狀態在統計集合數量的時候是危險的,因為回使得集合數量增多,但其實並不用擔心這個問題,仔細考察getf函式(查詢父節點)的時候就會發現f[a]=getf(f[a])這樣一段程式碼,原來getf函式不僅返回了每個節點的父親,還順便修改了每個節點的f[]值使它和他父親的f[]值一致,所以每當該節點或這個節點的子節點被merge再次合併地時候,這個節點的f[]就會被修改成正確值,但是問題就來了,萬一該節點沒有被再次合併怎麼辦?保險一點的操作應該是在merge完所有的節點之後再getf以下所有的節點,這樣所有的節點就正確了。

二.並查集的應用

1.用來合併集合元素,並確定結合數量,查尋元素屬於哪個集合。

2.在圖結構裡(圖裡的點便是元素),確定兩點是否處於聯通狀態,應用舉例:

(1)Kruskal法最小生成樹

思路:將所有的邊依照長度大小排序,依次從小到大進行行選澤,每次選中一邊,就將邊兩端的點用並查集合並,如果選擇的邊經並查集查詢已經聯通,那麼跳過這條邊

問題

給定一個有權值的圖,找出聯通圖內所有節點的最小路徑。

資料

6 9

2 4 11

3 5 13

4 6 3

5 6 4

2 3 6

4 5 7

1 2 1

3 4 9

程式碼

//
//  main.cpp
//  圖的最小生成樹之kruskal演算法
//
//  Created by 張嘉韜 on 16/3/18.
//  Copyright ? 2016年 張嘉韜. All rights reserved.
//

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int f[50];
struct line
{
    int u;
    int v;
    int w;
}lines[50];
int cmp(const void *a,const void *b)
{
    return (*(line *)a).w>(*(line *)b).w?1:-1;
}
void init(int n)
{
    for(int i=1;i<=n;i++) f[i]=i;
}
int getf(int a)
{
    if(f[a]==a) return a;
    else
    {
        f[a]=getf(f[a]);
        return f[a];
    }
}
int  merge(int a,int b)
{
    int t1,t2;
    t1=getf(a);
    t2=getf(b);
    if(t1!=t2)
    {
        f[t2]=t1;
        return 1;
    }
    else return 0;
}
int main(int argc, const char * argv[]) {
    freopen("/Users/zhangjiatao/Desktop/input.txt","r",stdin);
    int n,m,sum;
    sum=0;
    cin>>n>>m;
    init(n);
    for(int i=0;i<=m-1;i++)
    {
        cin>>lines[i].u>>lines[i].v>>lines[i].w;
    }
    qsort(lines,m,sizeof(lines[0]),cmp);
    for(int i=0;i<=m-1;i++) cout<<lines[i].w<<" ";
    cout<<endl;
    for(int i=0;i<=m-1;i++)
    {
        if(merge(lines[i].u,lines[i].v)==1)
        {
            sum+=lines[i].w;
            cout<<i+1<<" ";
        }
    }
    cout<<endl;
    cout<<sum<<endl;
    return 0;
}

三.並查集的模板

1.問題

給定m組n個元素之間的關係,問這些元素屬於個集合

2.輸入

#include <iostream>
#include <cstring>
using namespace std;
int f[50];
void init(int n)
{
    for(int i=1;i<=n;i++) f[i]=i;
}
int getf(int a)
{
    if(f[a]==a) return a;
    else
    {
        f[a]=getf(f[a]);
        return f[a];
    }
}
void merge(int a,int b)
{
    int t1,t2;
    t1=getf(a);
    t2=getf(b);
    if(t1!=t2)
    {
        f[t2]=t1;
    }
}
void print(int n)
{
    for(int i=1;i<=n;i++) cout<<"("<<i<<")"<<f[i]<<" ";
    cout<<endl;
}
int main(int argc, const char * argv[]) {
    freopen("/Users/zhangjiatao/Desktop/input.txt","r",stdin);
    int n,m,a,b,sum;
    sum=0;
    cin>>n>>m;
    init(n);
    for(int i=1;i<=m;i++)
    {
        cin>>a>>b;
        merge(a,b);
        print(n);
    }
    for(int i=1;i<=n;i++)
    {
    	f[i]=getf(i);
	}
    for(int i=1;i<=n;i++)
        if(f[i]==i) sum++;
    cout<<sum<<endl;
    return 0;
}



10 8

1 2

3 4

5 2

4 6

2 6

8 7

9 7

1 6

2 4