1. 程式人生 > >python機器學習案例系列教程——最小生成樹(MST)的Prim演算法和Kruskal演算法

python機器學習案例系列教程——最小生成樹(MST)的Prim演算法和Kruskal演算法

最小生成樹MST

一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。

也就是說,用原圖中有的邊,連線n個節點,保證每個節點都被連線,且使用的邊的數目最少。

最小權重生成樹

在一給定的無向圖G=(V,E)中,(u,v)代表連線頂點u與頂點 v的邊(即),而 w(u,v)代表此邊的權重,若存在 TE的子集(即)且為無迴圈圖,使得

w(t)=(u,v)tw(u,v)

w(T)最小,則此 TG 的最小生成樹。

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

應用:

例如:要在n個城市之間鋪設光纜,主要目標是要使這 n 個城市的任意兩個之間都可以通訊,但鋪設光纜的費用很高,且各個城市之間鋪設光纜的費用不同,因此另一個目標是要使鋪設光纜的總費用最低。這就需要找到帶權的最小生成樹

Prim演算法

1)、輸入:一個加權連通圖,其中頂點集合為V,邊集合為E

2)、初始化:Vnew={x},其中x為集合V中的任一節點(起始點),Enew={},為空;

3)、重複下列操作,直到Vnew=V

  • a.在集合E中選取權值最小的邊<u,v>,其中u為集合

    Vnew中的元素,而v不在Vnew集合當中,並且vV(如果存在有多條滿足前述條件即具有相同權值的邊,則可任意選取其中之一);

  • b.將v加入集合Vnew中,將<u,v>邊加入集合Enew中;

4)、輸出:使用集合VnewEnew來描述所得到的最小生成樹

Kruskal演算法簡述

假設 WN=(V,E)是一個含有n 個頂點的連通網,則按照克魯斯卡爾演算法構造最小生成樹的過程為:先構造一個只含 n 個頂點,而邊集為空的子圖,若將該子圖中各個頂點看成是各棵樹上的根結點,則它是一個含有 n 棵樹的一個森林。之後,從網的邊集

E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖,也就是說,將這兩個頂點分別所在的兩棵樹合成一棵樹;反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之。依次類推,直至森林中只有一棵樹,也即子圖中含有 n-1條邊為止。

迴圈中可加入已加入MST的點的數量的判斷,有可能提前結束迴圈,提高效率。

下面是hdu1233的原始碼,一個用Prim演算法,另一個用Kruskal,標準的MST問題。

#include <cstdio>
#include <algorithm>
using namespace std;

typedef int weight_t; 

#define SIZE 101

int N;

//圖的鄰接矩陣
weight_t Graph[SIZE][SIZE];

//各頂點到中間結果的最短距離,始終維護
weight_t D[SIZE];

//標誌位
bool Flag[SIZE];

//Prim演算法,返回MST的長度
weight_t Prim(){
    //初始化陣列
    fill(D,D+SIZE,INT_MAX);
    fill(Flag,Flag+SIZE,false);

    //初始化第一個計算的點
    D[1] = 0;

    weight_t ans = 0;

    for(int i=1;i<=N;++i){
        //找出距離中間結果最近的點
        int k = -1;
        for(int j=1;j<=N;++j)
            if ( !Flag[j] && ( -1 == k || D[j] < D[k] ) )
                k = j;

        //將k點加入中間結果
        Flag[k] = true;
        ans += D[k];

        //更新剩餘點到中間結果的最短距離
        for(int j=1;j<=N;++j)
            if ( !Flag[j] && Graph[k][j] < D[j] )
                D[j] = Graph[k][j];
    }

    return ans;
}

bool read(){
    scanf("%d",&N);
    if ( 0 == N ) return false;

    for(int i=0;i<N*(N-1)/2;++i){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        Graph[a][b] = Graph[b][a] = w;
    }

    return true;
}

int main(){
    while( read() ){
        printf("%d\n",Prim());
    }
    return 0;
}
#include <cstdio>
#include <algorithm>
using namespace std;

typedef int weight_t; 

#define SIZE 101

//並查集結構
int Father[SIZE];
void init(int n){for(int i=0;i<=n;Father[i]=i++);}
int find(int x){return Father[x]==x?x:Father[x]=find(Father[x]);}
void unite(int x,int y){Father[find(y)]=Father[find(x)];}

int N;

//邊結構
struct edge_t{
    int s;
    int e;
    weight_t w;
}Edge[SIZE*SIZE/2];
int ECnt = 0;

//過載,用於邊排序
bool operator < (edge_t const&lhs,edge_t const&rhs){
    if ( lhs.w != rhs.w ) return lhs.w < rhs.w;
    if ( lhs.s != rhs.s ) return lhs.s < rhs.s;
    return lhs.e < rhs.e;
}

//生成邊
inline void mkEdge(int a,int b,weight_t w){
    if ( a > b ) swap(a,b);

    Edge[ECnt].s = a;
    Edge[ECnt].e = b;
    Edge[ECnt++].w = w;
}

//Kruskal演算法,vn是點的數量,en是邊的數量,返回MST的長度
weight_t Kruskal(int vn,int en){
    init(vn);//並查集初始化
    sort(Edge,Edge+en);//邊排序

    weight_t ans = 0;
    for(int i=0;i<en;++i){
        //該邊已存在於MST中
        if ( find(Edge[i].s) == find(Edge[i].e) )
            continue;

        //將該邊加入MST
        ans += Edge[i].w;
        unite(Edge[i].s,Edge[i].e);
        --vn;

        //MST已完全生成
        if ( 1 == vn ) break;
    }

    return ans;
}

bool read(){
    scanf("%d",&N);
    if ( 0 == N ) return false;

    ECnt = 0;
    for(int i=0;i<N*(N-1)/2;++i){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        mkEdge(a,b,w);
    }

    return true;
}

int main(){
    while( read() ){
        printf("%d\n",Kruskal(N,ECnt));
    }
    return 0;
}