【數據結構】 最小生成樹(三)——prim算法
上一期介紹到了kruskal算法,這個算法誕生於1956年,重難點就是如何判斷是否形成回路,此處要用到並查集,不會用當然會覺得難,今天介紹的prim算法在kruskal算法之後一年(即1957年)誕生,長江後浪推前浪,前浪死在沙灘上,既然後發明,那一定有很多優點吧?具體又如何用代碼實現?比kruskal算法好在哪裏?不用擔心,小編會一一解答。
prim算法的思想:
prim算法的主要思想就是讓一棵小樹不斷得到新的樹邊,直到長成所有節點都在樹集合中的大樹。具體做法如下:首先先得來思考,最初的小樹是什麽樣子的?當然是初始化成一個根節點。那麽這棵小樹又如何長大?此時所有的節點就只有兩種情況—要麽已經在集合中,要麽還等待選擇。無論是kruskal算法,還是prim算法,都用到了貪心算法,既然是最小生成樹,那就要選擇最小的邊,prim算法不太一樣,它所選擇的是已經在集合中的節點的連向未選節點所形成的邊中最小權重的邊,將其未選節點添加進樹邊集合中。明確了這兩個問題,就可以想出以下步驟:
1)構造最初的待選邊表:這個表包含三種元素分別是已選節點,未選節點和相連邊的權值,在最初只有一個根節點,在程序實現上,多把0節點作為根節點,所以每一個已選節點賦初值為0,那麽未選節點分別為1,2,3,4…,n-1,n,假設存儲圖的矩陣的是二維數組map,那麽權值部分則是map[0][1],map[0][2],map[0][3]…map[0][n]。
2)不斷循環,直到未選節點只剩最後一個:從待選邊表找到一條最短邊,並加入樹邊集合;然後修改待選邊表,在樹邊集合中不停找出新的節點和當前的未選節點形成連邊,如果這條邊比原有最小值少就修改這條邊。
這樣最小生成樹就構建完成了,接著我們來說一說代碼實現方法。
代碼實現:
#include<iostream> #include<cmath> using namespace std; int n,map[1000][1000],k; struct tree{ //三元結構體 int yx; int dx; int cost; }; void prim() { int v; tree wait[1000];//待選邊表 for(int i=0;i<=n-1;i++) { wait[i].yx=0; wait[i].dx=i+1; wait[i].cost=map[0][i+1];//初始化 } for(int i=0;i<=n-2;i++)//不斷找到樹邊 { k=i; for(int j=i+1;j<n-1;j++) if(wait[j].cost<wait[k].cost) k=j;//找到最短邊 swap(wait[i],wait[k]);//交換 v=wait[i].dx;//新節點 for(int j=i+1;j<n-1;j++) { if(wait[j].cost<map[v][wait[j].dx])//修改待選邊表 { wait[j].cost=map[v][wait[j].dx]; wait[j].yx=v; } } } for(int i=1;i<=n;i++) { cout<<wait[i].yx<<" "<<wait[i].dx<<" "<<wait[i].cost; cout<<endl; } } int main() { cin>>n; for(int i=0;i<=n-1;i++) for(int j=0;j<=n-1;j++) cin>>map[i][j]; //do something prim(); return 0; }
正常樣例圖示:
//此圖是從mooc上copy來的
一般情況下待選邊表長這樣。
Prim算法的優點:
比起kruskal算法來講,prim算法可以不用判斷是否產生回路,因為在待選邊表中不停計算的過程中,可以有效避免產生回路的情況,可以不學並查集(有時還要學習堆),快速惡補,並且適用於稠密圖,其時間復雜度只與節點數量有關,在特定情況下可以更快的執行完程序。
Prim算法 的缺點:
prim算法適用於稠密圖,但是稀疏圖還是kruskal算法更勝一籌,並且prim算法更難理解,小編現在還有點懵,寫兩道題就好了,下棋將會帶來最小生成樹例題題解。
欲知後事如何,且聽下回分解……
【數據結構】 最小生成樹(三)——prim算法