給定乙個無向圖,如果它任意兩個頂點都聯通並且是一棵樹,那麼我們就稱之為生成樹(spanning tree)。如果是帶權值的無向圖,那麼權值之和最小的生成樹,我們就稱之為最小生成樹(mst, minimum spanning tree)。
我們由最小生成樹的定義,可以延伸出乙個修建道路的問題:把無向圖的每個頂點看作村莊,計畫修建道路使得可以在所有村莊之間通行。把每個村莊之間修建道路的費用看作權值,那麼我們就可以得到乙個求解修建道路的最小費用的問題。
常見求解最小生成樹的演算法有kruskal演算法和prim演算法。由於篇幅問題再此對於prim演算法,就不多做解釋了。現在我們看看kruskal演算法,是怎麼來求解最小生成樹的問題。
1、kruskal演算法描述
kruskal演算法是基於貪心的思想得到的。首先我們把所有的邊按照權值先從小到大排列,接著按照順序選取每條邊,如果這條邊的兩個端點不屬於同一集合,那麼就將它們合併,直到所有的點都屬於同乙個集合為止。至於怎麼合併到乙個集合,那麼這裡我們就可以用到乙個工具——-並查集(不知道的同學請移步:here
)。換而言之,kruskal演算法就是基於並查集的貪心演算法。
2、kruskal演算法流程
對於圖g(v,e),以下是演算法描述:
[cpp]view plain
copy
?輸入: 圖g
輸出: 圖g的最小生成樹
具體流程:
(1)將圖g看做乙個森林,每個頂點為一棵獨立的樹
(2)將所有的邊加入集合s,即一開始s = e
(3)從s中拿出一條最短的邊(u,v),如果(u,v)不在同一棵樹內,則連線u,v合併這兩棵樹,同時將(u,v)加入生成樹的邊集e』
(4)重複(3)直到所有點屬於同一棵樹,邊集e』就是一棵最小生成樹
輸入: 圖g我們用現在來模擬一下kruskal演算法,下面給出乙個無向圖b,我們使用kruskal來找無向圖b的最小生成樹。輸出: 圖g的最小生成樹
具體流程:
(1)將圖g看做乙個森林,每個頂點為一棵獨立的樹
(2)將所有的邊加入集合s,即一開始s = e
(3)從s中拿出一條最短的邊(u,v),如果(u,v)不在同一棵樹內,則連線u,v合併這兩棵樹,同時將(u,v)加入生成樹的邊集e'
(4)重複(3)直到所有點屬於同一棵樹,邊集e'就是一棵最小生成樹
首先,我們將所有的邊都進行從小到大的排序。排序之後根據貪心準則,我們選取最小邊(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
?#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 條邊為止...