最小生成樹(一)

簡介

在一個無向連通圖中,如果存在一個連通子圖包含原圖中所有的節點和部分邊,且這個子圖不存在迴路,那麼我們稱這個子圖爲原圖的一棵生成樹。在帶權圖中,所有的生成樹中邊權最小的一課或者幾棵稱爲最小生成樹。

Kruskal算法

算法思想

Kruskal算法比較簡單, 實際上是一種貪心的方式。不斷從未選的邊的集合當中選擇權值最小的,並且不和已選的點(初始已選的點的集合是空集)構成迴路的邊,將邊的兩個端點加入我們的已選點的集合當中,如此往復。
支撐這個算法有個小的定理,我們可以描述爲:
在要求解得連通圖中,任選一些點屬於集合A,剩餘的點屬於集合B,那麼我們選擇一條權值最小的邊,有個條件是這條邊的兩個頂點分屬於集合A和集合B,那麼這條邊一定包含於這個連通圖的一棵最小生成樹當中。
證明可以使用替換法。

算法操作

  1. 構建點集(if necessary)。
  2. 構建邊集。
  3. 按照邊的權值大小進行排序。
  4. 使用並查集檢查合併集合。

如何判斷我們選擇的邊已經在已選的集合中了呢?如果我們選擇的邊的集合的兩個端點在集合中,那麼這條邊一定和原來的集合構成迴路,因此這條邊我們是不能選擇的。只需要判斷roota和rootb關係即可,和之前的不矛盾。

下面兩道題目練習一下,第一道是很明顯的題目,練手用。
傳送門:還是暢通工程

#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#define N 6000
using namespace std;

//最小生成樹
struct Edge{
    int a,b;
    int length;
    bool operator < (const Edge &B)const{
        return length<B.length;
    }
}edge[N];

int Tree[N];  //並查集處理集合問題
int findRoot(int x){
    if(Tree[x]==-1)
        return x;
    else{
        int root=findRoot(Tree[x]);
        Tree[x]=root;
        return root;
    }
}
int main(){
    //freopen("test.txt","r",stdin);
    int n;
    while(scanf("%d",&n)!=EOF &&n!=0){
        memset(Tree,-1,sizeof(Tree));
        memset(edge,0,sizeof(edge));
        for(int i=1;i<=n*(n-1)/2;i++){
            scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].length);
        }
        sort(edge+1,edge+1+n*(n-1)/2);
        int re=0;
        for(int i=1;i<=n*(n-1)/2;i++){
            int roota=findRoot(edge[i].a);
            int rootb=findRoot(edge[i].b);
            if(roota!=rootb){
                re+=edge[i].length;
                Tree[roota]=rootb;
            }
        }
        printf("%d\n",re);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章