標籤:std 運算 原來 不清楚 clu 距離 idt 端點 相同
首先總結一下概念:
什麼是最小產生樹呢?
一個有 n 個結點的連通圖的產生樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。最小產生樹可以用kruskal(克魯斯卡爾)演算法或prim(普裡姆)演算法求出。
個人認為kruskal演算法還是比較優秀的。
所以此篇重點討論kruskal演算法。
1.概覽
Kruskal演算法是一種用來尋找最小產生樹的演算法,由Joseph Kruskal在1956年發表。用來解決同樣問題的還有Prim演算法和Boruvka演算法等。三種演算法都是貪婪演算法的應用。和Boruvka演算法不同的地方是,Kruskal演算法在圖中存在相同權值的邊時也有效。
圖例描述:
首先第一步,我們有一張圖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。最後成功的圖就是右:
3.簡單證明Kruskal演算法
對圖的頂點數n做歸納,證明Kruskal演算法對任意n階圖適用。
歸納基礎:
n=1,顯然能夠找到最小產生樹。
歸納過程:
假設Kruskal演算法對n≤k階圖適用,那麼,在k+1階圖G中,我們把最短邊的兩個端點a和b做一個合併作業,即把u與v合為一個點v‘,把原來接在u和v的邊都接到v‘上去,這樣就能夠得到一個k階圖G‘(u,v的合并是k+1少一條邊),G‘最小產生樹T‘可以用Kruskal演算法得到。
我們證明T‘+{<u,v>}是G的最小產生樹。
用反證法,如果T‘+{<u,v>}不是最小產生樹,最小產生樹是T,即W(T)<W(T‘+{<u,v>})。顯然T應該包含<u,v>,否則,可以用<u,v>加入到T中,形成一個環,刪除環上原有的任意一條邊,形成一棵更小權值的產生樹。而T-{<u,v>},是G‘的產生樹。所以W(T-{<u,v>})<=W(T‘),也就是W(T)<=W(T‘)+W(<u,v>)=W(T‘+{<u,v>}),產生了矛盾。於是假設不成立,T‘+{<u,v>}是G的最小產生樹,Kruskal演算法對k+1階圖也適用。
由數學歸納法,Kruskal演算法得證。
首先提示一下,畢竟是棵樹,我們決不能讓它形成環,所以要引入並查集。當發現一條路徑,判斷如果兩個點的祖先已經一樣了,說明這兩個點之前已被納入最小產生的樹的範疇,納入則成環,故此時不將其併入並查集(若並查集概念不清楚的可以訪問https://baike.baidu.com/item/%E5%B9%B6%E6%9F%A5%E9%9B%86/9388442?fr=aladdin,這個很容易弄清楚)。
還是附一下題:
1090: [視頻]最小產生樹(模版 kruskal演算法 元問題by scy)時間限制: 1 Sec 記憶體限制: 128 MB題目描述
【題目描述】 一個有n個點的連通無向圖,有m條無向邊,每條邊有一個長度c, 如果串連所有點,只需要從m條無向邊中選n-1條,為什嗎? 現在要求這n-1條邊的長度和最小。
以上就是最小產生樹的概念。
【輸入格式】 第一行輸入 n和 m (1<=n<=1000,n-1<=m<=50 0000) 下來 m 行,每行三個數 x,y,c,表示點 x 和 點 y 有一條距離為 c 的無向邊。0<c<=20 【輸出格式】 一行一個整數,即串連n個點的最小長度和。 【範例1輸入】 5 5 1 2 1 2 3 1 3 4 2 4 5 1 5 1 1 【範例1輸出】 4 【範例2輸入】 7 9 1 6 5 5 1 6 7 4 1 6 7 8 5 3 2 4 5 3 4 2 4 2 3 5 3 1 7 【範例2輸出】 21
接下來我們用代碼說話:
#include<cstdio>
#include<algorithm>
using namespace std;
struct edge{//結構體來存邊的資訊
int a,b,c;
// bool operator < (const edge &x) const 重載<運算子,當然為了結構體的比較你在外面寫函數也行
// { return c<x.c; }
}e[500000];//邊的資訊比較多,這裡不要吃虧!開大一點
int fa[1005];//並查集
int n,m;
bool cmp(const edge &x1,const edge &x2)
{
return x1.c<x2.c;
}
int find(int x)//找x的老祖先
{
if (fa[x]!=x) return fa[x]=find(fa[x]);//都是套路,遞迴壓縮路徑防逾時
return fa[x];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) fa[i]=i;//並查集的初始化
for (int i=1;i<=m;i++) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
sort(e+1,e+m+1,cmp);
int t=0;//計邊數
int ans=0;//計答案
for (int i=1;i<=m;i++)
{
int k1=find(e[i].a);
int k2=find(e[i].b);
if (k1!=k2)//太好了!說明可以合并!
{
fa[k2]=k1;
ans+=e[i].c;
t++;
if (t==n-1) break;
}
}
printf("%d\n",ans);
return 0;
}
caioj1090最小產生樹之kruskal演算法【模板元問題】