【算法】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 <bitsstdc++.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 <bitsstdc++.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中加,就既可以求单源最短路径,又可以求最小生成树了。

原文地址:https://www.cnblogs.com/zhangjiuding/p/7725832.html