最小生成樹詳解
最小生成樹概念:
一個有 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 |
無 | 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;
}