一、最小生成樹
(一)生成樹
在圖的BFS和DFS算法中,我們可以得到圖中頂點的一個線性序列,如果我們按照訪問的次序將這些頂點之間的邊連起來可以獲得一棵樹,我們將其稱之為生成樹。以下是一個圖的兩種生成樹:
這是一個無向連通圖:
其BFS樹(從BFS的過程中獲得的樹)為
其DFS樹(從DFS過程中獲得的生成樹)為:
(二)最小生成樹
對於一個帶權圖而言,總權值最小的生成樹為最小生成樹。
那么對於一個有V個頂點的帶權圖(網絡)而言,其生成樹必有V個頂點,V-1條邊,那么在生成最小生成樹時,我們必須要遵守以下三點准則:
(i)只能使用網絡中的邊進行構造;
(ii)用n-1條邊來聯結n個頂點;
(iii)所選用的邊不能構成回路;
注意:最小生成樹不一定唯一,當邊的權重相同時可能出現多種最小樹;
二、最小生成樹的生成算法
對於一個帶有V個頂點的網絡G=<V,E>,我們介紹兩種常用的最小生成樹生成算法。
I.Kruskal算法
Kruskal算法的基本思想為:①以G中的所有頂點來構造一個森林T(即V個只有一個結點的樹的集合);②從G的邊集E中選取權值最小的那一條邊,並將它加入到T中,注意不要形成回路;③重復第二步,直到所有頂點都已聯結或者當T中的邊數<V-1時,E=∅,前一種情況表示最小生成樹已經生成,后一種情況表示該網絡無最小生成樹;
下圖為Kruskal算法生成最小生成樹的一個例子:
Kruskal算法的偽代碼描述:
Kruskal:
T =0; //T為最小生成樹的集合,初始為空
while ((T contains less than n-1 edges) && (E not Empty)) {
choose an edge (v, w) from E of lowest cost;
delete (v, w) from E;
if ((v, w) does not create a cycle in T) add (v, w) to T;
else discard (v, w);
}
if (T contains fewer than n-1 edges)
cout<<“no spanning tree”<<endl;
算法中需要找到E中權值最小的一條邊,我們可以使用最小堆來實現這一步驟,最小堆的結點為邊結點<邊的開始位置(head),邊的結束位置(tail),權值(cost)>。
如何判斷一條邊在T內是否生成回路呢?我們可以用到並查集來實現這個步驟:並查集中的Find操作是搜索單元所在的集合,並返回該集合的名字;通過Find(tail),Find(head)是否相同就可以判斷該邊加入后是否會形成回路;如果不會,那么使用並查集的Union功能函數將兩個集合合並,那么兩個連通分量也就合並到了最小生成樹中;
附:Kruskal算法正確性的證明:(來自Sartaj Sahni所著《Fundamentals of Data Structure in C++》)
II.Prim算法
Prim算法也是不斷迭代進行的(類似於BFS)。
對於一個帶權圖G=<V,E>,算法將頂點集G分為兩個互不相交的部分,V=Vmst∪(V-Vmst),其中Vmst是最小生成樹的點的集合,后者是不在最小生成樹中的點的集合(記為N);只考慮聯通圖的情況下,Vmst與T之間至少有一條邊相連,我們稱之為橋;
算法的核心思想:每一輪迭代中,挑選出權值最小的橋Em(u,v),u∈Vmst,v∈N,將頂點v加入到Vmst中,刪除E;重復上述步驟直到Vmst中有n個頂點,n-1條邊或者E為空;
下面是一個例子:
Prim算法偽代碼描述:
TV = {0};
for (T = ∅; T contains fewer than n-1 edges; add (u, v) to T)
{
Let (u, v) be a least-cost edge such that u ∈ TV and v ∈TV;
if (there is no such edge) break;
add v to TV;
}
if (T contains fewer than n-1 edges)
cout<<“no spanning tree”<<endl;
Prim算法需要找出Vmst所有橋的權值最小橋,故也需要用到最小堆來實現這一步驟;
算法實現。。。emm。。。以后有時間再補吧