最小生成樹算法

正文
      所謂最小生成樹,就是在一個具備N個頂點的帶權連通圖G中,若是存在某個子圖G',其包含了圖G中的全部頂點和一部分邊,且不造成迴路,而且子圖G'的各邊權值之和最小,則稱G'爲圖G的最小生成樹。
      由定義咱們可得知最小生成樹的三個性質:
      • 最小生成樹不能有迴路。
      • 最小生成樹多是一個,也多是多個。
      • 最小生成樹邊的個數等於頂點的個數減一。html

本文將介紹兩種最小生成樹的算法,分別爲克魯斯卡爾算法(Kruskal Algorithm)和普利姆算法(Prim Algorithm)。node

1、克魯斯卡爾算法(Kruskal Algorithm)ios

克魯斯卡爾算法的核心思想是:在帶權連通圖中,不斷地在邊集合中找到最小的邊,若是該邊知足獲得最小生成樹的條件,就將其構造,直到最後獲得一顆最小生成樹。算法

克魯斯卡爾算法的執行步驟:
第一步:在帶權連通圖中,將邊的權值排序;
第二步:判斷是否須要選擇這條邊(此時圖中的邊已按權值從小到大排好序)。判斷的依據是邊的兩個頂點是否已連通,若是連通則繼續下一條;若是不連通,那麼就選擇使其連通。
第三步:循環第二步,直到圖中全部的頂點都在同一個連通份量中,即獲得最小生成樹。ide

下面我用圖示法來演示克魯斯卡爾算法的工做流程,以下圖:spa

 

首先,將圖中全部的邊排序(從小到大),咱們將以此結果來選擇。排序後各邊按權值從小到大依次是:.net

HG < (CI=GF) < (AB=CF) < GI < (CD=HI) < (AH=BC) < DE < BH < DF3d

接下來,咱們先選擇HG邊,將這兩個點加入到已找到點的集合。這樣圖就變成了,如圖code

 

繼續,此次選擇邊CI(當有兩條邊權值相等時,可隨意選一條),此時需作判斷。htm

判斷法則:當將邊CI加入到已找到邊的集合中時,是否會造成迴路?
     1.若是沒有造成迴路,那麼直接將其連通。此時,對於邊的集合又要作一次判斷:這兩個點是否在已找到點的集合中出現過?
          ①.若是兩個點都沒有出現過,那麼將這兩個點都加入已找到點的集合中;
          ②.若是其中一個點在集合中出現過,那麼將另外一個沒有出現過的點加入到集合中;
          ③.若是這兩個點都出現過,則不用加入到集合中。
     2.若是造成迴路,不符合要求,直接進行下一次操做。

根據判斷法則,不會造成迴路,將點C和點I連通,並將點C和點I加入到集合中。如圖:

繼續,此次選擇邊GF,根據判斷法則,不會造成迴路,將點G和點F連通,並將點F加入到集合中。如圖:

繼續,此次選擇邊AB,根據判斷法則,不會造成迴路,將其連通,並將點A和點B加入到集合中。如圖:

繼續,此次選擇邊CF,根據判斷法則,不會造成迴路,將其連通,此時這兩個點已經在集合中了,因此不用加入。如圖:

繼續,此次選擇邊GI,根據判斷法則,會造成迴路,以下圖,直接進行下一次操做:

 繼續,此次選擇邊CD,根據判斷法則,不會造成迴路,將其連通,並將點D加入到集合中。如圖:

繼續,此次選擇邊HI,根據判斷法則,會造成迴路,直接進行下一次操做。
繼續,此次選擇邊AH,根據判斷法則,不會造成迴路,將其連通,此時這兩個點已經在集合中了,因此不用加入。
繼續,此次選擇邊BC,根據判斷法則,會造成迴路,直接進行下一次操做。
繼續,此次選擇邊DE,根據判斷法則,不會造成迴路,將其連通,並將點E加入到集合中。如圖:

繼續,此次選擇邊BH,根據法則,會造成迴路,進行下一次操做。
最後選擇邊DF,根據法則,會造成迴路,不將其連通,也不用加入到集合中。

好了,全部的邊都遍歷完成了,全部的頂點都在同一個連通份量中,咱們獲得了這顆最小生成樹。

經過生成的過程能夠看出,可否獲得最小生成樹的核心問題就是上面所描述的判斷法則。
那麼,咱們如何用算法來描述判斷法則呢?我認爲只須要三個步驟便可:
    ⒈將某次操做選擇的邊XY的兩個頂點X和Y和已找到點的集合做比較,若是
         ①這兩個點都在已找到點的集合中,那麼return 2;
         ②這兩個點有一個在已找到點的集合中,那麼return 1;
         ③這兩個點都不在一找到點的集合中,那麼return 0;
    ⒉當返回值爲0或1時,可斷定不會造成迴路;
    ⒊當返回值爲2時,斷定能造成迴路的依據是:假如能造成迴路,設能造成迴路的點的集合中有A,B,C,D四個點,那麼以點A爲起始點,繞環路一週後必能回到點A。若是能回到,則造成迴路;若是不能,則不能造成迴路。

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

const int size = 128; 
int n;
int father[size];
int rank[size];

//把每條邊成爲一個結構體,包括起點、終點和權值 
typedef struct node
{
    int val;
    int start;
    int end;    
}edge[SIZE * SIZE / 2];

//把每一個元素初始化爲一個集合 
void make_set()
{
    for(int i = 0; i < n; i ++){
        father[i] = i;
        rank[i] = 1;    
    }    
    return ;
}

//查找一個元素所在的集合,即找到祖先 
int find_set(int x)
{
    if(x != father[x]){
        father[x] = find_set(father[x]);    
    }    
    return father[x];
}

//合併x,y所在的兩個集合:利用Find_Set找到其中兩個
//集合的祖先,將一個集合的祖先指向另外一個集合的祖先。
void Union(int x, int y)
{
    x = find_set(x);    
    y = find_set(y);
    if(x == y){
        return ;    
    }
    if(rank[x] < rank[y]){
        father[x] = find_set(y);    
    }
    else{
        if(rank[x] == rank[y]){
            rank[x] ++;    
        }    
        father[y] = find_set(x);
    }
    return ;
}

bool cmp(pnode a, pnode b)
{
    return a.val < b.val;    
}

int kruskal(int n) //n爲邊的數量 
{
    int sum = 0;
    make_set();
    for(int i = 0; i < n; i ++){   //從權最小的邊開始加進圖中 
        if(find_set(edge[i].start) != find_set(edge[i].end)){
            Union(edge[i].start, edge[i].end);
            sum += edge[i].val;    
        }    
    }
    return sum;    
}

int main()
{
    while(1){
        scanf("%d", &n);    
        if(n == 0){
            break;    
        }
        char x, y;
        int m, weight;
        int cnt = 0;
        for(int i = 0; i < n - 1; i ++){
            cin >> x >> m; 
            //scanf("%c %d", &x, &m);    
            //printf("%c %d ", x, m);
            for(int j = 0; j < m; j ++){
                cin >> y >> weight; 
                //scanf("%c %d", &y, &weight);
                //printf("%c %d ", y, weight);    
                edge[cnt].start = x - 'A';
                edge[cnt].end = y - 'A';
                edge[cnt].val = weight;
                cnt ++;
            }
        }
        
        sort(edge, edge + cnt, cmp); //對邊按權從小到大排序 
        cout << kruskal(cnt) << endl; 
    }    
}
View Code

 

 2、普利姆算法(Prim Algorithm)

普利姆算法的核心步驟是:在帶權連通圖中,從圖中某一頂點v開始,此時集合U={v},重複執行下述操做:在全部u∈U,w∈V-U的邊(u,w)∈E中找到一條權值最小的邊,將(u,w)這條邊加入到已找到邊的集合,而且將點w加入到集合U中,當U=V時,就找到了這顆最小生成樹。

其實,由步驟咱們就能夠定義查找法則:在全部u∈U,w∈V-U的邊(u,w)∈E中找到一條權值最小的邊。

ok,知道了普利姆算法的核心步驟,下面我就用圖示法來演示一下工做流程,如圖:

首先,肯定起始頂點。我以頂點A做爲起始點。根據查找法則,與點A相鄰的點有點B和點H,比較AB與AH,咱們選擇點B,以下圖。並將點B加入到U中。

繼續下一步,此時集合U中有{A,B}兩個點,再分別以這兩點爲起始點,根據查找法則,找到邊BC(當有多條邊權值相等時,可選任意一條),以下圖。並將點C加入到U中。

繼續,此時集合U中有{A,B,C}三個點,根據查找法則,咱們找到了符合要求的邊CI,以下圖。並將點I加入到U中。

繼續,此時集合U中有{A,B,C,I}四個點,根絕查找法則,找到符合要求的邊CF,以下圖。並將點F加入到集合U中。

繼續,依照查找法則咱們找到邊FG,以下圖。並將點G加入到U中。

繼續,依照查找法則咱們找到邊GH,以下圖。並將點H加入到U中。

 

 繼續,依照查找法則咱們找到邊CD,以下圖。並將點D加入到U中。

 

 繼續,依照查找法則咱們找到邊DE,以下圖。並將點E加入到U中。

 

此時,知足U = V,即找到了這顆最小生成樹。
附註:prim算法最小生成樹的生成過程當中,也有可能構成迴路,需作判斷。判斷的方法和克魯斯卡爾算法同樣。

int prime(int cur)
{
    int index;
    int sum = 0;
    memset(visit, false, sizeof(visit));
    visit[cur] = true;
    for(int i = 0; i < m; i ++){
        dist[i] = graph[cur][i];    
    }
    
    for(int i = 1; i < m; i ++){
        
        int mincost = INF;
        for(int j = 0; j < m; j ++){
            if(!visit[j] && dist[j] < mincost){
                mincost = dist[j];
                index = j;    
            }    
        }
        
        visit[index] = true;
        sum += mincost;
        
        for(int j = 0; j < m; j ++){
            if(!visit[j] && dist[j] > graph[index][j]){
                dist[j] = graph[index][j];
            }    
        }    
    } 
    return sum;    
}
View Code

 

 

轉載:http://blog.csdn.net/fengchaokobe/article/details/7521780
        http://www.cnblogs.com/aiyelinglong/archive/2012/03/26/2418707.html

相關文章
相關標籤/搜索