最小生成树算法模板

Prim算法

  本文主要讲解的是Prim算法,该算法用于寻找最小生成树(MST)。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。(上一篇博客是Kruskal算法,如果没学过的话建议先移步去学习Kruskal算法) 
  Prim算法于1930年由捷克数学家沃伊捷赫•亚尔尼克发现;并在1957年由美国计算机科学家罗伯特•普里姆独立发现;1959年,艾兹格•迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。既然有了Kruskal算法,为什么还需要Prim算法呢?原因是,Kruskal算法更适合求稀疏图的最小生成树;而对于稠密图而言,Prim算法则更为高效优异。 
  下面简单叙述Prim算法的实现思想方法: 
  1) 输入原图,设原图顶点所成之集为U,边集为E。 
  2) 新建点集V,任意在原图中选择一个点作为起始点,将该点加入集合V。并新建边集E’为空。 
  3) 重复下列操作,直至V=U: 
  在边集E中选取权值最小的边(u, v),其中u为集合V中的元素,而v为集合U\V中的元素(属于U但不属于V)。(如果存在多条满足条件的边,则可任意选取其一)将点v加入集合V中,将边(u, v)加入集合E’中。 
  4) 此时,集合E’即为所求的最小生成树。 
  可以看出,Prim算法在第3步的时候也是运用了贪心的思想。为什么这样做是正确的呢?下给出简单的反证法证明: 
  反设由Prim算法生成的不是最小生成树,记为G。又设存在一个生成树Gmin使得w(Gmin)<<w(G),则在Gmin中至少存在一条边(u, v)不属于G。不妨将边(u, v)加入G中,得到一个环,且(u, v)不是该环的最长边。而Prim算法中,每次选择的都是权值最小的边,产生矛盾。故假设不成立,原命题得证。 
  在Prim算法中,较为关键的一步是:“选取一条符合条件的、权值最小的边”。注意到,此算法在不断选取边、构造生成树的过程中,每选取一条边,也就是新加入了一个点。被加入的各点间是彼此连通的(区别于Kruskal算法的过程)。形象点的描述,这棵树就像是从一个起始点延伸开去的。因此,只需用一个数组visited[i],来记录某点i是否已经被加入(即是否连通)。如果i没有被连通,那么从任意连通的点到此点i的边即是符合条件的。同时,寻找权值最小的边也很简单,只需用一个数组lowcost[i],来存储当前情况下走一条边到点i的最短距离(若无法走一条边直接到达,则该值为无穷大)。数组lowcost[i]初始化为从起始点到各点之间的距离。之后,每选取一条边(也即新连通了一个点)时,更新一下lowcost[i]数组即可。 
  下为一个Prim算法求最小生成树的实例。(绿色的表示已被选取) 
图1图2图3图4图5图6图7图8 
  如果你已经弄清出了上述思想,就不难自己写出程序。下面还是以最小生成树的模板题为例:HDU1863,链接为http://acm.hdu.edu.cn/showproblem.php?pid=1863。 
  给出我写的Prim算法模板(也即HDU1863题解)如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 102
#define INF 1000000
using namespace std;

int n, m;
int graph[maxn][maxn];
int lowcost[maxn];
int visited[maxn];

void createGraph() {
    memset(graph, 0, sizeof(graph));
    memset(lowcost, 0, sizeof(lowcost));
    memset(visited, 0, sizeof(visited));
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            graph[i][j] = INF;
    int u, v, w;
    for(int i = 0; i < m; i++) {
        cin >> u >> v >> w;
        graph[u][v] = graph[v][u] = w;
    }
}

void prim() {
    int sum = 0, cnt = 0;
    visited[1] = 1;
    for(int i = 1; i <= n; i++) {
        lowcost[i] = graph[1][i];
    }
    while(true) {
        int minn = lowcost[1], k = 1;
        for(int j = 2; j <= n; j++) {
            if(!visited[j] && lowcost[j] < minn) {
                minn = lowcost[j];
                k = j;
            }
        }
        if(k == 1) break;
        sum += minn;
        cnt++;
        visited[k] = 1;
        for(int t = 1; t <= n; t++) {
            if(!visited[t] && lowcost[t] > graph[k][t]) {
                lowcost[t] = graph[k][t];
            }
        }
    }
    if(cnt == n-1) printf("%d\n", sum);
    else printf("?\n");
}

int main() {
    while(cin >> m >> n && m) {
        createGraph();
        prim();
    }
    return 0;
} 

猜你喜欢

转载自blog.csdn.net/qiuyumin430/article/details/80889546