並查集與最小生成樹Kruskal算法

1、什麼是並查集

在計算機科學中,並查集是一種樹型的數據結構,用於處理一些不交集的合併及查詢問題。有一個聯合-查找算法(union-find algorithm)定義了兩個用於次數據結構的操做:node

  • Find:肯定元素屬於哪個子集。它能夠被用來肯定兩個元素是否屬於同一子集。
  • Union:將兩個子集合併成一個集合。

2、主要操做

  • 初始化:把每一個點所在的集合初始化爲其自身。
for(int i=1;i<=n;i++)
    f[i]=i;
  • 查找:查找元素所在的集合,即根節點。
int find(int x)
{
    while(f[x]!=x)
        x=f[x];
    return x;
}
  • 合併:將兩個元素所在的集合合併爲一個集合。
void Union(int x1,int x2)
{
    int t1=find(x1);
    int t2=find(x2);
    if(t1!=t2)
        f[t2]=t1;
}

3、優化

上面的代碼看似簡潔,可是每一次find操做的時間複雜度爲O(H),H爲樹的高度,因爲咱們沒有對樹作特殊處理,因此樹的不斷合併可能會使樹嚴重不平衡,最壞狀況每一個節點都只有一個子節點。c++

因此在find函數裏採用路徑壓縮算法

int find(int x)       //查找x元素所在的集合,回溯時壓縮路徑
{
    if (x != f[x])
    {
        f[x] = find(f[x]); 
        //從x結點搜索到祖先結點所通過的結點都指向該祖先結點
    }         
    return f[x];
}

4、模板題

洛谷P3367【模板】並查集數據結構

#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[10002];
int find(int x)
{
    if(x!=f[x])
        f[x]=find(f[x]);
    return f[x];
}
void Union(int x1,int x2)
{
    int t1=find(x1);
    int t2=find(x2);
    if(t1!=t2) //祖先不同 
        f[t2]=t1; //把t2的祖先變爲x1的祖先t1 
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        f[i]=i;
    for(int i=0;i<m;i++)
    {
        int z,x,y;
        cin>>z>>x>>y;
        if(z==1)
            Union(x,y); 
        else
        {
            if(find(x)!=find(y))cout<<"N"<<endl;
            else cout<<"Y"<<endl;
        }
    }
    return 0;
}

5、最小生成樹

一個有n個結點的連通圖的生成樹是原圖的極小聯通子圖,期包含原圖的全部n個結點,且有保持圖連通的最少邊。函數

最小生成樹其實就是最小權重生成樹的簡稱。優化

Kruskal算法spa

  • 將圖的全部邊按照權值從小到大排序
  • 遍歷全部排好序的邊,若構不成迴路,則將該邊加入到集合中
  • 直到找出n-1條邊

例題:繁忙的都市code

#include<bits/stdc++.h>
using namespace std;
int n,m;
int s,maxm;
int p[100002];
struct node{
    int u;
    int v;
    int c;
}info[100002];

bool cmp(node x1,node x2)
{
    if(x1.c!=x2.c)return x1.c<x2.c;
    else if(x1.u!=x2.u) return x1.u<x2.u;
    else return x1.v<x2.v;
}
int find(int x)       //查找x元素所在的集合,回溯時壓縮路徑
{
    if (x!=p[x])
    {
        p[x]=find(p[x]);  
    }         
    return p[x];
}
void bcj(int x1,int x2)//把x2併入x1的集合
{
    int t1,t2;//存儲祖先節點
    t1=find(x1);
    t2=find(x2);
    if(t1!=t2)p[t2]=t1;
}
int main()
{
    cin>>n>>m;//n就是頂點數,m是邊數
    for(int i=1;i<=n;i++)
    {
        p[i]=i;
    }
    for(int i=0;i<m;i++)
    {
        cin>>info[i].u>>info[i].v>>info[i].c;
    }
    sort(info,info+m,cmp);
    for(int i=0;i<m;i++)//遍歷全部的邊
    {
        if(find(info[i].u)!=find(info[i].v))
        {
            bcj(info[i].u,info[i].v);//把v併入u的集合
            maxm=max(maxm,info[i].c);
        }
    }
    cout<<n-1<<" "<<maxm;
    return 0;
}
相關文章
相關標籤/搜索