最小生成树之Prim算法的学习心得与个人总结
最小生成树:MST
从4.24到今天5.4(除去29,30,1三天假期),完成了hdu1162 , hdu1102 , hdu3371 , hdu1233 , hdu1879 , hdu1301 , hdu1864 。由于我的入手学习方法错了,导致花的时间比较长,效率低。错误方法是看完白书和黑书直接做模板题,导致了对算法细节的理解一塌糊涂。建议应该先看各种博文,理解模板每一步的意义,再做题。此处感谢TZL对我及时的批评和建议,受用无穷。
现在谈一下最小生成树之Prim算法。这个算法是对图中的点进行贪心,从起始点开始,到图中个点的边权值最小的纳入最小生成树集合中的过程。算法中涉及到了一个二维数组和两个一维数组,其中这两个一维数组意义重大,曾一度搞得我把算法改的一塌糊涂。
二维数组map[ a ][ b ] :存储从点a到点b的边的权值。
一维数组low[ i ] :存储记录顶点集合T'中各顶点距离到顶点集合T中最小的边的权值。这时,从起始点与for循环中到达的点i一起进行贪心,与起始点和i点相连的路径中权值最小将存进low[i]。我一开始理解成当前点所连接的边的最小权值。这是不全面的。
一维数组v[i] :记录顶点集合T'中各顶点距离到顶点集合T中哪个顶点距离最近。类型可以选择int 或者bool 。对应的值时0,1 或者true,false 。当v[i] = 1 / true 时,表示点i属于集合T 。
根据个人习惯或者题目要求,一般选择从点1或者点0开始贪心。这时对应的边界值,初始化都是不同的。一定要上下对应!
Prim模板:(从点1开始贪心)
以 Hdu1863 - 通畅工程 为例题,这是一道MST的裸题。
题意:n为路的数目,m为村庄数;若无法连通,则输出-1,否则输出最小权值;当n为0时,直接结束不用输出结果。
代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> const int max = 0x7ffffff; int map[105][105], v[105], low[105]; int n,m; int MinTree(int n) { int min,flag,i,j,sum; //初始化 memset(v, 0, sizeof(v)); memset(low, 0, sizeof(low)); v[1] = 1; //从村庄1开始贪心 sum = 0; for(i=2; i<=m; i++) //此处的范围应是m村庄数,i为2~m中的一个村庄 { low[i] = map[1][i]; //因为从点1开始贪心,所以是map[1][i] } for(i=2; i<=m; i++) //此处的范围应是m村庄数;如果是n边数的话,当最小生成树已经构建完的话,而边数小于村庄数, //则for会一直执行,而if则不成立,结果就是return -1. { min = max; flag = -1; //将与点1连通的边中权值最小的边找到 for(j=1; j<=m; j++) { if(!v[j] && low[j]<min) { min = low[j]; flag = j; } } if(min == max) return -1; //此图不连通 //sum += low[flag]; sum += min; v[flag] = 1; //标记 for(j=1; j<=m; j++) //更新边 { if(!v[j] && low[j]>map[flag][j]) { low[j] = map[flag][j]; } } } return sum; //返回最小值的和 } int main() { int i,j,a,b,c,ans; while(scanf("%d%d",&n,&m),n) { //初始化 for(i=1; i<=m; i++) for(j=1; j<=m; j++) { if(i==j) map[i][j] = 0; else map[i][j] = max; } //从点a到点b的权值是c for(i=1; i<=n; i++) { scanf("%d%d%d",&a,&b,&c); if(c < map[a][b]) { map[a][b] = c; map[b][a] = c; } } ans = MinTree(n); if(ans<=0) printf("?\n"); else printf("%d\n", ans); } return 0; }