【演算法】prim演算法(最小生成樹)(與Dijkstra演算法的比較)
最小生成樹:
生成樹的定義:給定一個無向圖,如果它的某個子圖中任意兩個頂點都互相連通並且是一棵樹,那麼這棵樹就叫做生成樹。(Spanning Tree)
最小生成樹的定義:在生成樹的基礎上,如果邊上有權值,那麼使得邊權和最小的生成樹叫做最小生成樹。(Minimum Spanning Tree )
解決生成樹有兩種常用的演算法:Kruskal演算法和prim演算法。
這裡我們講的是prim演算法求生成樹的解法。
演算法思想:
ans = 0;(表示權值和)
1.在無向圖的基礎上,想象我們有一個點的集合X(初始狀態為空)。
2.在集合X中加入一個初始點x,用這個初始點更新其他點離集合X的距離mincost[ ],標記初始點為使用過(使用過:加入集合X)。
3.找一個未使用過(集合X外的點)的離集合X最小距離最小的點,找到了這樣一個點,將這個點加入集合X,ans += 這個與集合X的距離,
用這個新加入的點更新其他點離集合X的最小距離mincost[ ],標記新加入的點為使用過,繼續執行第3步;找不到這樣一個點,則進入第4步。
4.輸出ans。
mincost[i] 表示點i離集合X的最小距離。(離集合X中所有點中最近的點的距離)
簡單來說就是:
想象一下,有10張面額不同的毛爺爺在你面前,每次只能拿1張,只能拿5次,你肯定會每次都拿這n張(n<=10)中最大的那張,
這樣拿5次,讓總額最大。這個演算法也是同樣的道理,不斷的加入可觸及的最近的點,最後權值一定是最小的。
額,當然10張不同的毛爺爺是不存在的。所以一分都拿不到,還是看程式碼吧:
程式碼:
#include <bits\stdc++.h>
using namespace std;
#define INF 2147483647
#define MAX_V 1000
#define MAX_E 2000
int cost[MAX_V][MAX_V]; // cost[i][j] 表示頂點i到頂點j的權值,不存在時為INF
int mincost[MAX_V]; // mincost[i] 表示i點與集合X的最小距離
bool used[MAX_V]; // used[i]表示點i是否在集合中
int V; //頂點數
//表示從點x產生的最小生成樹,這麼考慮是因為整個圖可能不連通
int prim(int x){
//最初X集合為空,每個點到集合X的最小距離都是INF
for(int i = 0;i < V; ++i){
mincost[i] = INF;
used[i] = false;
}
//將點x與集合X的距離置為0,第一次集合X會加入點x
mincost[x] = 0;
int res = 0;
while(true){
int v = -1;
//找到離集合X最近的點,第一次加入點x
for(int u = 0;u < V; ++u){
if(!used[u] && (v == -1 || mincost[u] < mincost[v])) v = u;
}
//如果所有點可達的點都加入集合X中了,就跳出
if(v == -1) break;
used[v] = true;
res += mincost[v];
mincost[v] = 0; //把點v加入到集合X中,這一步幫助理解,可寫可不寫 ,因為cost[v][v] = 0
//用新加入的點v更新其他點離與集合X的最小距離
for(int u = 0;u < V; ++u){
mincost[u] = min(mincost[u] , cost[v][u]);
}
}
return res;
}
int main(){
}
與Dijkstra演算法的比較
Prim演算法與Dijkstra演算法都是從某個點出發,不斷加入最近的點。最終都要把所有可以加的點加完。
Prim演算法是求最小生成樹,Dijkstra是求單源最短路徑。
來個Dijkstra求單源最短路徑的程式碼,與Prim演算法比較一下:
#include <bits\stdc++.h>
using namespace std;
#define INF 2147483647
#define MAX_V 1000
#define MAX_E 2000
//單源最短路徑問題(Dijkstra演算法)
int cost[MAX_V][MAX_V]; //cost[u][v]表示e = (u,v)的權值
int d[MAX_V]; //頂點s出發的最短距離 //不同處1
bool used[MAX_V]; //標記使用過的點
int V; //頂點數
void dijkstra(int s){
fill(d, d+V, INF);
fill(used, used + V, INF);
d[s] = 0;
while(true){
int v = -1;
//找到一個距離最近的沒有使用過的點
for(int u = 0;u < V; u++){
if(!used[u] && (v == -1 || d[u] < d[v])) v = u;
}
//如果所有的點都被使用過了,則break
if(v == -1) break;
//標記當前點被使用過了
used[v] = true;
//更新這個找到的距離最小的點所連的點的距離
for(int u = 0;u < V; u++){
d[u] = min(d[u], d[v] + cost[v][u]); //不同處2
}
}
}
int main(){
}
我們可以看到程式碼基本上是一樣的,只有
不同處1:Djikstra中用d[i]表示i點離源點的最短距離,Prim中用mincost[i] 表示i點與集合X的距離。
不同處2:Djikstra中更新d[u] = min( d[u] , d[v] + cost[v][u] ); 用新加入的點更新其他點與源點的最小距離。
Prim中更新mincost[u] = min( mincost[u] , cost[v][u] ); 用新加入的點更新點i與集合X的最小距離。
我認為只用加幾行程式碼,無論是在prim中加,還是在Dijkstra中加,就既可以求單源最短路徑,又可以求最小生成樹了。