1. 程式人生 > >最小生成樹算法詳解(prim+kruskal)

最小生成樹算法詳解(prim+kruskal)

span 實現 比較 info 開始 += width map end

最小生成樹概念:

一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。 最小生成樹可以用kruskal(克魯斯卡爾)算法或prim(普裏姆)算法求出。最小生成樹其實是最小權重生成樹的簡稱。

prim:

普裏姆算法(Prim算法),圖論中的一種算法,可在加權連通圖裏搜索最小生成樹。意即由此算法搜索到的邊子集所構成的樹中,不但包括了連通圖裏的所有頂點(英語:Vertex (graph theory)),且其所有邊的權值之和亦為最小。

prim算法求最小生成樹的時候和邊數無關,和頂點樹有關,所以適合求解稠密網的最小生成樹。

prim算法的步驟包括:

1. 將一個圖分為兩部分,一部分歸為點集U,一部分歸為點集V,U的初始集合為{V1},V的初始集合為{ALL-V1}。

2. 針對U開始找U中各節點的所有關聯的邊的權值最小的那個,然後將關聯的節點Vi加入到U中,並且從V中刪除(註意不能形成環)。

3. 遞歸執行步驟2,直到V中的集合為空。

4. U中所有節點構成的樹就是最小生成樹。

實現圖解:

圖例說明不可選可選已選(Vnew

技術分享圖片

此為原始的加權連通圖。每條邊一側的數字代表其權值。 - - -

技術分享圖片

頂點D被任意選為起始點。頂點ABEF通過單條邊與D相連。A是距離D最近的頂點,因此將A及對應邊AD以高亮表示。 C, G A, B, E, F D

技術分享圖片

下一個頂點為距離DA最近的頂點。BD為9,距A為7,E為15,F為6。因此,FDA最近,因此將頂點F與相應邊DF以高亮表示。 C, G B, E, F A, D
技術分享圖片 算法繼續重復上面的步驟。距離A為7的頂點B被高亮表示。 C B, E, G A, D, F

技術分享圖片

在當前情況下,可以在CEG間進行選擇。CB為8,EB為7,GF為11。E最近,因此將頂點E與相應邊BE高亮表示。 C, E, G A, D, F, B

技術分享圖片

這裏,可供選擇的頂點只有CGCE為5,GE為9,故選取C,並與邊EC一同高亮表示。 C, G A, D, F, B, E

技術分享圖片

頂點G是唯一剩下的頂點,它距F為11,距E為9,E最近,故高亮表示G及相應邊EG G A, D, F, B, E, C

技術分享圖片

現在,所有頂點均已被選取,圖中綠色部分即為連通圖的最小生成樹。在此例中,最小生成樹的權值之和為39。 A, D, F, B, E, C, G

再來一張比較容易懂的圖片:

(a):一個無向圖,記錄了各點之間的權值關系

(b):在圖中選擇一個與{v1}連接最小的點v3

(c):選擇一個與{v1,v3}連接最小的點v6

(d):選擇一個與{v1,v3,v6}連接最小的點v4

(e):選擇一個與{v1,v3,v6,v4}連接最小的點v2

(f):選擇一個與{v1,v3,v6,v4,v2}連接最小的點v5

生成完畢。

技術分享圖片

Kruskal算法(並查集實現)

Kruskal是一種用來尋找最小生成樹的算法,在剩下的所有未選取的邊中,找最小邊,如果和已選取的邊構成回路,則放棄,選取次小邊。

實現過程

1).記Graph中有v個頂點,e個邊

2).新建圖Graphnew,Graphnew中擁有原圖中相同的e個頂點,但沒有邊

3).將原圖Graph中所有e個邊按權值從小到大排序

4).循環:從權值最小的邊開始遍歷每條邊 直至圖Graph中所有的節點都在同一個連通分量中 if 這條邊連接的兩個節點於圖Graphnew中不在同一個連通分量中 添加這條邊到圖Graphnew

  圖例描述:

技術分享圖片

首先第一步,我們有一張圖Graph,有若幹點和邊

技術分享圖片

將所有的邊的長度排序,用排序的結果作為我們選擇邊的依據。這裏再次體現了貪心算法的思想。資源排序,對局部最優的資源進行選擇,排序完成後,我們率先選擇了邊AD。這樣我們的圖就變成了下圖

技術分享圖片

在剩下的變中尋找。我們找到了CE。這裏邊的權重也是5

技術分享圖片

依次類推我們找到了6,7,7,即DF,AB,BE。

技術分享圖片

下面繼續選擇, BC或者EF盡管現在長度為8的邊是最小的未選擇的邊。但是現在他們已經連通了(對於BC可以通過CE,EB來連接,類似的EF可以通過EB,BA,AD,DF來接連)。所以不需要選擇他們。類似的BD也已經連通了(這裏上圖的連通線用紅色表示了)。最後就剩下EG和FG了。當然我們選擇了EG。

代碼:

prim;

#include<stdio.h>
#include<string.h>
#include <iostream>
#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(false);\
    cin.tie(0);    cout.tie(0);
#define MAX 0x3f3f3f3f
using namespace std;
int logo[1010];//用來標記0和1  表示這個點是否被選擇過
int map1[1010][1010];//鄰接矩陣用來存儲圖的信息
int dis[1010];//記錄任意一點到這個點的最近距離
int n;//點個數
int prim()
{
    int i,j,now;
    int sum=0;
    /*初始化*/
    for(i=1; i<=n; i++)
    {
        dis[i]=MAX;
        logo[i]=0;
    }
    /*選定1為起始點,初始化*/
    for(i=1; i<=n; i++)
    {
        dis[i]=map1[1][i];
    }
    dis[1]=0;
    logo[1]=1;
    /*循環找最小邊,循環n-1次*/
    for(i=1; i<n; i++)
    {
        now=MAX;
        int min1=MAX;
        for(j=1; j<=n; j++)
        {
            if(logo[j]==0&&dis[j]<min1)
            {
                now=j;
                min1=dis[j];
            }
        }
        if(now==MAX)
            break;//防止不成圖
        logo[now]=1;
        sum+=min1;
        for(j=1; j<=n; j++)//添入新點後更新最小距離
        {
            if(logo[j]==0&&dis[j]>map1[now][j])
                dis[j]=map1[now][j];
        }
    }
    if(i<n)
        printf("?\n");
    else
        printf("%d\n",sum);
}
int main()
{
    while(scanf("%d",&n),n)//n是點數
    {
        int m=n*(n-1)/2;//m是邊數
        memset(map1,0x3f3f3f3f,sizeof(map1));//map是鄰接矩陣存儲圖的信息
        for(int i=0; i<m; i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            if(c<map1[a][b])//防止重邊
                map1[a][b]=map1[b][a]=c;
        }
        prim();
    }
}

kruskal:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m,sum;
struct node
{
    int start,end,power;//start為起始點,end為終止點,power為權值
} edge[5050];
int pre[5050];

int cmp(node a, node b)
{
    return a.power<b.power;//按照權值排序
}

int find(int x)//並查集找祖先
{
    if(x!=pre[x])
    {
        pre[x]=find(pre[x]);
    }
    return pre[x];
}

void merge(int x,int y,int n)//並查集合並函數,n是用來記錄最短路中應該加入哪個點
{
    int fx=find(x);
    int fy=find(y);
    if(fx!=fy)
    {
        pre[fx]=fy;
        sum+=edge[n].power;
    }
}
int main()
{
    while(~scanf("%d", &n), n)//n是點數
    {
        sum=0;
        m=n*(n-1)/2;//m是邊數,可以輸入
        int i;
        int start,end,power;
        for(i=1; i<=m; i++)
        {
            scanf("%d %d %d", &start, &end, &power);
            edge[i].start=start,edge[i].end=end,edge[i].power=power;
        }
        for(i=1; i<=m; i++)
        {
            pre[i]=i;
        }//並查集初始化
        sort(edge+1, edge+m+1,cmp);
        for(i=1; i <= m; i++)
        {
            merge(edge[i].start,edge[i].end,i);
        }
        printf("%d\n",sum);
    }
    return 0;
}

最小生成樹算法詳解(prim+kruskal)