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

 

最小生成樹概念:

一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的全部 n 個結點,而且有保持圖連通的最少的邊。 最小生成樹能夠用kruskal(克魯斯卡爾)算法或prim(普里姆)算法求出。最小生成樹實際上是最小權重生成樹的簡稱。node

prim:

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

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

prim算法的步驟包括:算法

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

2. 針對U開始找U中各節點的全部關聯的邊的權值最小的那個,而後將關聯的節點Vi加入到U中,而且從V中刪除(注意不能造成環)。spa

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

4. U中全部節點構成的樹就是最小生成樹。code

實現圖解:blog

 

圖例 說明 不可選 可選 已選(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; }
相關文章
相關標籤/搜索