最小生成樹之Kruskal演算法

2022-08-31 19:54:23 字數 3835 閱讀 7863

給定乙個無向圖,如果它任意兩個頂點都聯通並且是一棵樹,那麼我們就稱之為生成樹(spanning tree)。如果是帶權值的無向圖,那麼權值之和最小的生成樹,我們就稱之為最小生成樹(mst, minimum spanning tree)。

我們由最小生成樹的定義,可以延伸出乙個修建道路的問題:把無向圖的每個頂點看作村莊,計畫修建道路使得可以在所有村莊之間通行。把每個村莊之間修建道路的費用看作權值,那麼我們就可以得到乙個求解修建道路的最小費用的問題。

常見求解最小生成樹的演算法有kruskal演算法和prim演算法。由於篇幅問題再此對於prim演算法,就不多做解釋了。現在我們看看kruskal演算法,是怎麼來求解最小生成樹的問題。

1、kruskal演算法描述

kruskal演算法是基於貪心的思想得到的。首先我們把所有的邊按照權值先從小到大排列,接著按照順序選取每條邊,如果這條邊的兩個端點不屬於同一集合,那麼就將它們合併,直到所有的點都屬於同乙個集合為止。至於怎麼合併到乙個集合,那麼這裡我們就可以用到乙個工具——-並查集(不知道的同學請移步:here

)。換而言之,kruskal演算法就是基於並查集的貪心演算法。

2、kruskal演算法流程

對於圖g(v,e),以下是演算法描述:

[cpp]view plain

copy

print

?輸入: 圖g  

輸出: 圖g的最小生成樹  

具體流程:  

(1)將圖g看做乙個森林,每個頂點為一棵獨立的樹  

(2)將所有的邊加入集合s,即一開始s = e  

(3)從s中拿出一條最短的邊(u,v),如果(u,v)不在同一棵樹內,則連線u,v合併這兩棵樹,同時將(u,v)加入生成樹的邊集e』  

(4)重複(3)直到所有點屬於同一棵樹,邊集e』就是一棵最小生成樹  

輸入: 圖g

輸出: 圖g的最小生成樹

具體流程:

(1)將圖g看做乙個森林,每個頂點為一棵獨立的樹

(2)將所有的邊加入集合s,即一開始s = e

(3)從s中拿出一條最短的邊(u,v),如果(u,v)不在同一棵樹內,則連線u,v合併這兩棵樹,同時將(u,v)加入生成樹的邊集e'

(4)重複(3)直到所有點屬於同一棵樹,邊集e'就是一棵最小生成樹

我們用現在來模擬一下kruskal演算法,下面給出乙個無向圖b,我們使用kruskal來找無向圖b的最小生成樹。

首先,我們將所有的邊都進行從小到大的排序。排序之後根據貪心準則,我們選取最小邊(a,d)。我們發現頂點a,d不在一棵樹上,所以合併頂點a,d所在的樹,並將邊(a,d)加入邊集e『。

我們接著在剩下的邊中查詢權值最小的邊,於是我們找到的(c,e)。我們可以發現,頂點c,e仍然不在一棵樹上,所以我們合併頂點c,e所在的樹,並將邊(c,e)加入邊集e』

不斷重複上述的過程,於是我們就找到了無向圖b的最小生成樹,如下圖所示:

3、kruskal演算法的時間複雜度

kruskal演算法每次要從都要從剩餘的邊中選取乙個最小的邊。通常我們要先對邊按權值從小到大排序,這一步的時間複雜度為為o(|elog|e|)。kruskal演算法的實現通常使用並查集,來快速判斷兩個頂點是否屬於同乙個集合。最壞的情況可能要列舉完所有的邊,此時要迴圈|e|次,所以這一步的時間複雜度為o(|e|α(v)),其中α為ackermann函式,其增長非常慢,我們可以視為常數。所以kruskal演算法的時間複雜度為o(|elog|e|)。

4、實戰演練

我們現在已經基本了解了kruskal演算法,讓我們來一道題目練練手:暢通工程

。這是一道非常基本的最小生成樹的應用,所以我就不做詳細說明了,這裡僅附上**以供參考:

[cpp]view plain

copy

print

?#include 

#include 

#define maxn 10000 + 10

using

namespace

std;  

intpar[maxn], rank[maxn];  

typedef

struct

node;  

node a[maxn];  

intcmp(

const

void

*a, 

const

void

*b)  

void

init(

intn)  

}  int

find(

intx)  

return

root;  

}  void

unite(

intx, 

inty)  

else

}  //n為邊的數量,m為村莊的數量

intkruskal(

intn, 

intm)  

}  //如果加入邊的數量小於m - 1,則表明該無向圖不連通,等價於不存在最小生成樹

if(nedge 

return

res;  

}  int

main()  

ans = kruskal(n, m);  

if(ans == -1) printf(

「?\n」

);  

else

printf(

「%d\n」

, ans);  

}  return

0;  

}  

#include
#include #define maxn 10000 + 10

using namespace std;

int par[maxn], rank[maxn];

typedef structnode;

node a[maxn];

int cmp(const void*a, const void *b)

void init(int n)

}int find(int x)

return root;

}void unite(int x, int y)

else

}//n為邊的數量,m為村莊的數量

int kruskal(int n, int m)

}//如果加入邊的數量小於m - 1,則表明該無向圖不連通,等價於不存在最小生成樹

if(nedge < m-1) res = -1;

return res;

}int main()

ans = kruskal(n, m);

if(ans == -1) printf("?\n");

else printf("%d\n", ans);

}return 0;

}當然要是覺得不夠爽,那就再送大家乙個大禮包:圖論500題

(這裡包含了絕大多數的圖論題目,對於每道題都有難度的註明)。



最小生成樹之Kruskal演算法

最小生成樹 kruskal演算法描述 該演算法是基於貪心的思想得到的。首先我們把所有的邊按照權值先從小到大排列,接著按照順序選取每條邊,如果這條邊的兩個端點不屬於同一集合,那麼就將它們合併,直到所有的點都屬於同乙個集合為止。合併頂點可以利用並查集,換而言之,kruskal演算法就是基於並查集的貪心演...

最小生成樹之kruskal演算法

先構造乙個只含 n 個頂點 而邊集為空的子圖,把子圖中各個頂點看成各棵樹上的根結點,之後,從網的邊集 e 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖,即把兩棵樹合成一棵樹,反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之。依次類推,直到...

最小生成樹之kruskal演算法

克魯斯卡爾 kruskal 演算法過程 構造最小生成樹 u,te 1.置u的初值等於v 即包含有g中的全部頂點 te的初值為空集 即圖t中每乙個頂點都構成乙個連通分量 2.將圖g中的邊按權值從小到大的順序依次選取 若選取的邊未使生成樹t形成迴路,則加入te 否則捨棄,直到te中包含 n 1 條邊為止...