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)
<
<script type="math/tex" id="MathJax-Element-1"><</script>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算法求最小生成树的实例。(绿色的表示已被选取)
如果你已经弄清出了上述思想,就不难自己写出程序。下面还是以最小生成树的模板题为例: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;
}