首页 > 编程语言 >最小生成树Kruskal+Prim算法

最小生成树Kruskal+Prim算法

时间:2022-08-21 15:47:09浏览次数:84  
标签:Prim int Kruskal 最小 算法 权值 集合 节点

最小生成树

给定一张边带权的无向图G=(V,E),n=|V|,m=|E|。由 V中全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树。边的权值之和最小的生成树被称为无向图G的最小生成树。

定理:任意一棵最小生成树一定包含无向图中权值最小的边。

证明:反证法。假设无向图中存在一棵最小生成树不包含权值最小的边。设e=(u,v,w)是权值最小的边。把e加到树上,e会和树上从x到y的路经一起构成一个环,并且环上其他边的权值都比e大。因此,用e代替环上的其他任意一条边,会形成一棵权值更小的生成树,与假设矛盾。所以假设不成立,原命题成立。

Kruskal算法

1.算法思想:Kruskal算法就是基于上述定理。最初,可认为每个节点各自构成一棵仅包含一个点的树(各自为一个集合)。每次从未被选取的边中选取一条权值最小且该边的两个端点不在一棵树上(不在一个集合里),把该边加入到最小生成树,该边的两个端点合成一棵树(放入一个集合),直至所有的点都在一棵树上(都在一个集合里)。显而易见,判断两个端点是否在一个集合里与合并可以用并查集简单地完成。

2.算法流程:

(1)初始化,建立并查集,每个点各自构成一个集合;

(2)把所有边按照权值从小到大排序,依次扫描每条边(u,v,w);

(3)若u,v属于同一个集合,则继续扫描下一条边,否则合并u,v所在集合,并把w累加到答案ans里;

(4)当最小生成树的集合中的节点个数为n,或者合并的边数为n-1时,ans即为所求的最小生成树的边的权值之和。

3.时间复杂度为O(m log m)。

4.例题:洛谷P3366 https://www.luogu.com.cn/problem/P3366

AC代码如下:

 1  #include<bits/stdc++.h>
 2  #define pa pair<int,int>
 3  using namespace std;
 4  const int N=1e6+5;
 5  int n,m,fa[N],cnt,ans; 
 6  pair<int,pa> e[N];
 7  int f(int x)
 8  {
 9      if(x==fa[x]) return x;
10      return fa[x]=f(fa[x]);//路径压缩
11  }//并查集
12  void kruskal()
13  {
14      for(int i=1;i<=n;i++) fa[i]=i;//初始化,每个点各自为一个集合
15      sort(e+1,e+m+1);//排序
16      for(int i=1;i<=m;i++)
17      {
18          int u=e[i].second.first,v=e[i].second.second;
19          u=f(u);
20          v=f(v);
21          if(u!=v)//两个点是否在一个集合里
22          {
23              fa[u]=v;//合并
24              ans+=e[i].first;//累加答案
25              cnt++;//合并边数加1
26          }
27          if(cnt==n-1) break;//满足条件跳出循环
28      }
29      if(cnt==n-1) cout<<ans;//若扫描完所有边,合并边数仍小于n-1,则该图不连通
30      else cout<<"orz";
31  }
32  int main()
33  {
34      cin>>n>>m;
35      for(int i=1;i<=m;i++)
36      {
37          int x,y,w;
38          cin>>x>>y>>w;
39          e[i]=make_pair(w,make_pair(x,y));
40      }
41      kruskal();
42      return 0;
43  }

 

 

 

Prim算法

1.算法思想:Prim算法同样基于上述定理。最初,Prim算法仅确定1号节点属于最小生成树。设已经确定属于最小生成树的节点集合为T,剩余节点集合为S,每次找到两个端点分别属于集合S,T的权值最小的边(u,v,w),然后把点u从集合S中删除,加入到集合T中,并把w累加到答案中。

具体来说,可以维护一个数组d:若u属于S,则d[u]表示节点u与集合T中的节点之间权值最小的边的权值。若u属于T,则d[u]就等于u被加入T是选出的最小边的权值。而我们可以用一个标记数组vis,标记节点是否属于T,每次我们从未标记的节点中选出d值最小的,把它标记(新加入T)。同时扫描所有出边,更新另一个端点的d值。最后,最小生成树的权值之和就是d[1]+d[2]+......+d[n]。

2.算法流程:

(1)初始化,d[1]=0,其他节点d值全为正无穷;

(2)从未标记的节点中选出d值最小的u,把它标记vis[u]=1,并把d[u]累加到答案中;

(3)扫描(2)中选出节点所有出边,更新另一个端点的d值;

(4)重复(2)(3),直至所有点全被标记。

 3.注意事项:时间复杂度为O(n2),可以用二叉堆(优先队列priority_queue)优化到O(m log n)。但用二叉堆优化不如直接用Kruskal算法更加方便。因此,Prim算法主要用于稠密图,尤其是完全图的最小生成树的求解。

4.例题:洛谷P3366  https://www.luogu.com.cn/problem/P3366

AC代码如下:

 1 #include<bits/stdc++.h>
 2 #define pa pair<int,int>
 3 using namespace std;
 4 const int N=1e6+5;
 5 int n,m,vis[N],d[N],cnt,ans; 
 6 vector<pa>g[N]; 
 7 void prim()
 8 {
 9     memset(d,0x3f,sizeof(d));
10     memset(vis,0,sizeof(vis));
11     d[1]=0;//初始化
12     while(1)
13     {
14         int u=-1;
15         for(int i=1;i<=n;i++)
16             if(vis[i]==0&&(u==-1||d[u]>d[i])) u=i;//从未标记的节点中选出d值最小的u
17         if(u==-1||d[u]==0x3f3f3f3f) break;//所有节点被标记或该图不连通跳出循环
18         ans+=d[u];//累加答案
19         cnt++;//标记节点数+1
20         vis[u]=1;//标记
21         for(int i=0;i<g[u].size();i++)
22         {
23             int v=g[u][i].first,w=g[u][i].second;
24             if(vis[v]) continue;
25             d[v]=min(d[v],w);//更新所有出边另一个端点的d值
26         }
27     }
28     if(cnt==n) cout<<ans;//若被标记节点数不等于n,则该图不连通
29     else cout<<"orz";
30 }
31 int main()
32 {
33     cin>>n>>m;
34     for(int i=1;i<=m;i++)
35     {
36         int x,y,w;
37         cin>>x>>y>>w;
38         g[x].push_back(make_pair(y,w));
39         g[y].push_back(make_pair(x,w));
40     }
41     prim();
42     return 0;
43 }

练习:洛谷P2872 https://www.luogu.com.cn/problem/P2872 

   洛谷P4047 https://www.luogu.com.cn/problem/P4047

标签:Prim,int,Kruskal,最小,算法,权值,集合,节点
From: https://www.cnblogs.com/Snoww/p/16610075.html

相关文章

  • 11.垃圾回收相关算法
    目录11.1垃圾回收概述1.什么是垃圾2.为什么需要GC11.2垃圾回收相关算法1.标记阶段:引用计数算法2.标记阶段:可达性分析算法3.对象的finalization机制4.MAT与JProfiler的G......
  • Vue/uniapp使用雪花算法生成随机ID
    安装snowflake-id插件npmisnowflake-id 页面导入雪花插件importSnowflakeIdfrom"snowflake-id"; 方法内使用雪花算法constsnowflake=newSnowflak......
  • 算法总结
    1.最近请求次数写一个 RecentCounter 类来计算特定时间范围内最近的请求。请实现RecentCounter类:RecentCounter()初始化计数器,请求数为0。intping(intt)......
  • 1036 [SCOI2012]滑雪与时间胶囊 最小生成树 可以用prim做的DAG prim+kruskal不能做DAG
    链接:https://ac.nowcoder.com/acm/contest/26077/1036来源:牛客网题目描述a180285非常喜欢滑雪。他来到一座雪山,这里分布着M条供滑行的轨道和......
  • 进程调度算法
    操作系统有三大调度机制,分别是进程调度、内存页面置换和磁盘调度算法。进程调度算法定义进程调度算法也称CPU调度算法,毕竟进程是由CPU调度的,当CPU空闲时,操作系统......
  • Hadoop常见的文件格式及压缩算法
    前言 该文章中将会整理一些大数据中常见的文件格式及压缩算法的理论知识,作为后期实践的理论指导。理论+实践才会更方便用这些文件格式和压缩算法。    目前hadoop中......
  • C++primer练习16.1-14
    练习16.1::实例化就是模板通过实际调用而确定类型及其运算,抽象到具体练习16.2template<typenameT>intcompare(constT&v1,constT&v2){if(v1<v2)return-1;......
  • python a* 寻址算法 我是按照像素来判断是不是障碍物
    fromrandomimportrandintfromPILimportImageclassSearchEntry():def__init__(self,x,y,g_cost,f_cost=0,pre_entry=None):self.x=x......
  • 算法竞赛进阶指南 0x65 负环与差分约数
    这里与最短路密切相关可以使用spfa,利用spfa的原理(cnt数组),如果发现一个点是通过了超过n-1条边更新而来,那么就说明存在负环AcWing361.观光奶牛给定一张L个点、P条边的......
  • 欧几里得算法和扩展欧几里得算法
    欧几里得算法和扩展欧几里得算法概述本篇简要介绍欧几里得算法和扩展欧几里得算法欧几里得算法欧几里得算法就是辗转相除法,用于求两个数的最大公约数欧几里得算法:pub......