最小生成樹概念:
一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。 最小生成樹可以用kruskal(克魯斯卡爾)算法或prim(普里姆)算法求出。最小生成樹其實是最小權重生成樹的簡稱。
prim:
概念:普里姆算法(Prim算法),圖論中的一種算法,可在加權連通圖里搜索最小生成樹。意即由此算法搜索到的邊子集所構成的樹中,不但包括了連通圖里的所有頂點
圖例 | 說明 | 不可選 | 可選 | 已選(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 |
算法模板:
#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算法:
1.概覽
Kruskal算法是一種用來尋找最小生成樹的算法,在剩下的所有未選取的邊中,找最小邊,如果和已選取的邊構成回路,則放棄,選取次小邊。
2.實現過程
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。
代碼:
#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; }