最小生成樹概念:
一個有 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被任意選爲起始點。頂點A、B、E和F經過單條邊與D相連。A是距離D最近的頂點,所以將A及對應邊AD以高亮表示。 | C, G | A, B, E, F | D |
|
下一個頂點爲距離D或A最近的頂點。B距D爲9,距A爲7,E爲15,F爲6。所以,F距D或A最近,所以將頂點F與相應邊DF以高亮表示。 | C, G | B, E, F | A, D |
![]() |
算法繼續重複上面的步驟。距離A爲7的頂點B被高亮表示。 | C | B, E, G | A, D, F |
|
在當前狀況下,能夠在C、E與G間進行選擇。C距B爲8,E距B爲7,G距F爲11。E最近,所以將頂點E與相應邊BE高亮表示。 | 無 | C, E, G | A, D, F, B |
|
這裏,可供選擇的頂點只有C和G。C距E爲5,G距E爲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; }