最小生成树算法——Prim算法和Kruskal算法

本文详细解析了Prim和Kruskal两种最小生成树算法的原理与实现,包括Prim算法的“加点法”和Kruskal算法的贪心思想及并查集应用,并附带经典题目解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Prim算法理解,可以看这篇文章https://blog.youkuaiyun.com/yeruby/article/details/38615045

其实就是每次从当前树中外选取一个离树最近且不构成环的点,同时sum记录权值,然后把这个点加入树中,直到所有节点都被访问过,最小生成树生成成功,输出最小生成树的权值和。

下面是Prim算法的板子,和最短路有点相似,也称为“加点法”。


 
  1. //最小生成树Prim算法
  2. #include<iostream>
  3. #define INF 0x3f3f3f3f
  4. using namespace std;
  5. const int maxn = 110;
  6. int n,m;
  7. int G[maxn][maxn];
  8. int prim()
  9. {
  10. int mst[maxn],sum= 0,lowcost[maxn];
  11. //lowcost[i]: 表示以i为终点的边的最小权值,
  12. //当lowcost[i]=0说明以i为终点的边的最小权值=0,
  13. //也就是表示i点加入了MST
  14. //mst[i]:表示对应lowcost[i]的起点,
  15. //即说明边<mst[i],i>是MST的一条边,
  16. //当mst[i]=0表示起点i加入MST
  17. for( int i = 2;i<=n;i++){
  18. lowcost[i]=G[ 1][i];
  19. mst[i]= 1; //初始化lowcost数组为到起始点的距离,mst数组为1(即所有点的起点都是1)
  20. }
  21. mst[ 1]= 0; //起始点进树
  22. for( int i = 2;i<=n;i++){
  23. int minn=INF,minid= 0;
  24. for( int j = 2;j<=n;j++) //遍历lowcost数组,查询最小值
  25. if(minn>lowcost[j]&&lowcost[j]!= 0)
  26. minn=lowcost[minid=j];
  27. // printf("%d-%d=%d\n",mst[minid],minid,minn);
  28. lowcost[minid]= 0; //查询到的最短的点进树
  29. sum+=minn;
  30. for( int j= 2;j<=n;j++){ //节点进树后更新外面的点到树的最短距离并且mst记录其起点
  31. if(G[minid][j]<lowcost[j]){
  32. lowcost[j]=G[minid][j];
  33. mst[j]=minid;
  34. }
  35. }
  36. }
  37. return sum;
  38. }
  39. int main()
  40. {
  41. scanf( "%d %d",&n,&m);
  42. for( int i = 1;i<=n;i++)
  43. for( int j = 1;j<=n;j++)
  44. G[i][j]=INF; //初始化
  45. for( int i = 1;i<=m;i++){
  46. int a,b,cost;
  47. scanf( "%d %d %d",&a,&b,&cost);
  48. G[a][b]=G[b][a]=cost;
  49. }
  50. int cost=prim();
  51. printf( "%d\n",cost);
  52. return 0;
  53. }

其实也可以用一个visited数组来标记是否被访问过。

Kruskal算法(基于贪心思想和并查集的实现)

关于算法的理解可以看这篇博客https://blog.youkuaiyun.com/luoshixian099/article/details/51908175

不过上面博客的代码实现比较复杂。

关于并查集的理解可以看这篇文章https://blog.youkuaiyun.com/niushuai666/article/details/6662911

其实算法很好理解,就是先把图中所有边按照权值从小到大给他们排个序(贪心),然后每次取一条边,判断边的起点和终点是否在一个连通分量里面,如果不在就合并,(这里关键在于连通分量的查询和合并),直到构成一棵最小生成树。

一谈到贪心,我们就不能简单的去用,还要能证明,不然说不定就错了(QAQ,就像01背包一样)

具体的证明过程可以去百度,或者看紫书356页。

下面给出伪代码的实现


 
  1. 把所有边排序,记第i小的边为e[i](i<= 1<m)
  2. 初始化MST为空
  3. 初始化连通分量为空,让每个点自成一个独立的连通分量
  4. 初始化sum= 0
  5. for(i= 0;i<m(边的个数);i++)
  6. if(e[i].u(起点)和e[i].v(终点)不在同一个连通分量中){
  7. 把边e[i]加入MST中
  8. 合并e[i].u和e[i].v所在的连通分量
  9. sum+=e[i].w;(记录权值)
  10. }

下面是代码具体实现,不过涉及到间接排序(不知道的可以百度,有点绕),其实也可以开个结构数组排序。


 
  1. #include <iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. int p[ 110],w[ 110],r[ 110],u[ 110],v[ 110];
  5. //第i条边的两个端点序号和权值分别存储在u[i],v[i]和w[i]中
  6. //排序后第i小的序号存储在r[i]中(间接排序,排序的是对象的代号而不是其本身)
  7. int n,m;
  8. bool cmp(const int i ,const int j)//间接排序函数
  9. {
  10. return w[i]<w[j];
  11. }
  12. int find(int x)//并查集的find
  13. {
  14. return p[x]==x?x:p[x]=find(p[x]); //路径压缩
  15. }
  16. void init()//初始化并查集和边的序号
  17. {
  18. for( int i = 1;i<=n;i++)
  19. p[i]=i;
  20. for( int i = 1;i<=m;i++)
  21. r[i]=i;
  22. }
  23. int Kruskal()
  24. {
  25. int ans= 0;
  26. init();
  27. sort(r,r+m,cmp); //给边排序
  28. for( int i = 1;i<=m;i++){
  29. int e=r[i];
  30. int x=find(u[e]);
  31. int y=find(v[e]); //找出两个边所在集合的编号
  32. if(x!=y){
  33. ans+=w[e];
  34. p[x]=y;
  35. //如果在不同集合,合并
  36. }
  37. }
  38. return ans;
  39. }
  40. int main(int argc, char** argv) {
  41. while( scanf( "%d %d",&n,&m)!=EOF){
  42. for( int i = 0;i<m;i++)
  43. scanf( "%d %d %d",&u[i],&v[i],&w[i]);
  44. printf( "%d\n",Kruskal());
  45. }
  46. return 0;
  47. }

然后就是各种习题的练习,下面给出一些经典的题和题解。

POJ 1251Jungle Roads —— POJ1251题解

POJ 1287 Networking —— POJ1287题解

POJ 2031 Building a Space Station——POJ2031题解

POJ 2421 Constructing Roads —— POJ2421题解

ZOJ1586QS Network —— ZOJ1586题解

POJ1789Truck History —— POJ1789题解

POJ2349Arctic Network——POJ2349题解

POJ1751Highways——POJ1751题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值