《大話數據結構》中在“圖”的那一章節有這樣一個實例:假設你是電信實施工程師,需要為一個鎮的九個村庄架設通信網絡做設計。村庄位置大致如下圖,之間連線的數字表示村與村間的可通達直線距離(個別如v0與v6,v6與v8,v5與v7未測算距離是因為有高山或湖泊,不予考慮)。你們領導要求你必須用最小的成本完成這次任務。你說怎么辦?
我之前在某家設計院也是做網絡規划設計的,而且覺得很有實際意義,所以拿出來給大家分享一下。其實這個案例就是查找連通網的最小生成樹。這術語不懂也沒關系,我們不管它叫什么。
有人說,這還不簡單嘛,我憑着直覺就能做出來。不排除你有這種能力,有時候人類的“直覺”真的是強大,也不知道人類是如何進化到這么聰慧的。
有人說,把所有可能的方案羅列出來,再一一比對,找到最短路徑的那個方案,就行了。這確實是個好辦法,實際上我們也是這樣做的。那么怎么去羅列所有的方案呢?。
為了便於描述上面的圖,我們構造圖的鄰接矩陣。如下圖所示:
在單純的計算或者有規律的運算方面,我們很容易想到借助計算機來幫忙解決。 剛剛我提到人類的直覺是很強大的,比如有的人答題的時候直接省略了步驟得出了結果,而要他寫詳細的步驟可能他也說不太清。計算機是沒有直覺的,某些我們認為很簡單的東西她可能要運算很多次,而且你還得教她怎樣按照她自己“有限的能力”去執行,可是往往我們不知道怎么去教計算機,也就是具體的怎樣寫代碼。
這里介紹兩種思路,也就是兩個算法:
一、一種思路是這樣的:
從v0(可以任意選定一點,這里選v0)開始,找到與其相連的最短的路徑,定義兩個一維數組a,b。a存儲相關頂點的權值(邊長度),b存儲對應的頂點。路徑(b[i],i)的權值為a[i].i為0至8的整數,v0為起點至各點的權值如下所示(實際中我們用65535來代表無窮大)。
求a數組中的最小值,為10,對應的i為1,標記邊(v0,v1)並讓ai置為0,代表vi已經納入生成路徑了(此時i為1)。
然后從v1開始,排除a[i]=0對應的頂點vi,當(v1,vi)的權值小於此時a[i]的值,將前者替換掉后者,更新a數組的值。
求a數組中的最小值,為11,對應的i為5,標記邊(v0,v5)並讓a5置為0,代表v5已經納入生成路徑了。
然后從v5開始,排除a[i]=0對應的頂點vi,當(v5,vi)的權值小於此時a[i]的值,將前者替換掉后者,更新a數組的值。
下面的步驟依此類推,以表格表述。
上面這個一步步找到最優路徑的方法其實就是普里姆算法。是不是很巧妙呢?我猜是一個叫普里姆的人發明的方法。具體的代碼實現如下(從《大話數據結構》拷貝的):
二、一種思路是這樣的:
將各邊所對應的起始點及權值(邊長)按權值排序,整理成下表所示:
這種方法就是以邊為核心,每次搜索相關的最短邊,排除掉形成閉環的邊。
第一次找到的邊為(v4,v7)
第二次找到的邊為(v2,v8)
第三次找到的邊為(v0,v1)
第四次找到的邊為(v0,v5)
第五次找到的邊為(v1,v8)
第六次找到的邊為(v3,v7)
第七次找到的邊為(v1,v6)
第八次找到的邊為:這次按邊長排序輪到(v5,v6),但是此時與上面所找到這些邊形成了環路。過濾掉!
第九次找到的邊為:這次按邊長排序輪到(v1,v2),但是此時與上面所找到這些邊形成了環路。過濾掉!
第十次找到的邊為(v6,v7)
此后的邊均構成環路,過濾掉。
過程是很明晰的,但是算法的具體實現卻很精妙。也是用數組的模型,占位更新的思想去實現。每次確定了一條邊就在數組中相應位置用相關值做上標記。判斷是否形成環路的方法也是很巧妙的,橋接起了邊與起始點的關系:形成一個環路就是起始點首尾依次連接,最后回到起點。
下面貼上代碼及分析:
最終最小生成樹就是如下圖標黑所示:
對比兩個算法,克魯斯算法邊數少時,效率會非常高;而普里姆算法對於邊數非常多的情況會更好一些。
關於這兩個算法相信大家都大概了解了。或許有人會對上面算法的正確性存疑,可以在網上搜索一下相關證明或者驗證,我這邊提供一個鏈接作為參考http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html