1. 程式人生 > >【演算法】prim演算法(最小生成樹)(與Dijkstra演算法的比較)

【演算法】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中加,就既可以求單源最短路徑,又可以求最小生成樹了。