转载自——》https://www.cnblogs.com/ninedream/p/11203704.html
最小生成树:
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。简单来说就是有且仅有n个点n-1条边的连通图。
而最小生成树就是最小权重生成树的简称,即所有边的权值之和最小的生成树。
最小生成树问题一般有以下两种求解方式。
一、Prim算法
参考了Feynman的博客
Prim算法通常以邻接矩阵作为储存结构。
算法思路:以顶点为主导地位,从起始顶点出发,通过选择当前可用的最小权值边把顶点加入到生成树当中来:
1.从连通网络N={V,E}中的某一顶点U0出发,选择与它关联的具有最小权值的边(U0,V),将其顶点加入到生成树的顶点集合U中。
2.以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(U,V),把它的顶点加入到集合U中。如此继续下去,直到网络中的所有顶点都加入到生成树顶点集合U中为止。
模板题链接:Prim算法求最小生成树
朴素版时间复杂度O(n²)算法模板:
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int N = 500+10; int n,m; int g[N][N],dis[N],vis[N]; void prim() { memset(dis,0x1f,sizeof dis); dis[1]=0; for(int j=1;j<=n;j++) { int min_len=2e+9,k; for(int i=1;i<=n;i++) { if(!vis[i]&&dis[i]<min_len) { min_len=dis[i]; k=i; } } vis[k]=1; for(int i=1;i<=n;i++) { if(!vis[i]&&dis[i]>g[k][i]) dis[i]=g[k][i]; } } } int main() { scanf("%d%d",&n,&m); memset(g,0x1f,sizeof g); for(int i=1;i<=m;i++) { int u,v,w;scanf("%d%d%d",&u,&v,&w); g[u][v]=g[v][u]=min(g[u][v],w); //因为有重边,所以取min } prim(); int ans=0; for(int i=1;i<=n;i++)ans+=dis[i]; if(ans>1e7)printf("impossible\n"); else printf("%d\n",ans); return 0; }
与Dijkstra类似,Prim算法也可以用堆优化,优先队列代替堆,优化的Prim算法时间复杂度O(mlogn)模板(图的存储方式为前向星):
void Prim_heap(int point) { memset(dis,0x1f,sizeof(dis)); priority_queue<pair<int,int> > q; dis[point]=0; q.push(make_pair(0,1)); while(!q.empty()) { int k=q.top().second; q.pop(); v[k]=1; for(int i=h[k];i!=-1;i=edge[i].next) { int to=edge[i].to,w=edge[i].w; if(!v[to]&&dis[to]>w) { dis[to]=w; q.push(make_pair(-dis[to],to)); //优先队列大根堆变小根堆小骚操作:只需一个‘-’号; } } } for(int i=1;i<=n;i++)if(dis[i]==0x1f1f1f1f)flag=false; //判断是否不存在最小生成树 return ; }
二、Kruskal算法
相比于Prim算法,更常用的还是Kruskal,其原因在于Kruskal算法模板的代码量小而且思路易理解。
算法思路:先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。
步骤:
- 新建图G,G中拥有原图中相同的节点,但没有边;
- 将原图中所有的边按权值从小到大排序;
- 从权值最小的边开始,如果这条边连接的两个节点于图G中不在同一个连通分量中,则添加这条边到图G中;
- 重复3,直至图G中所有的节点都在同一个连通分量中。
简单来说就是以边为主导地位,每次选择权值最小的边,判断该边连接的两点是否连通,若不连通,则合并两点(合并操作以并查集实现)。记录合并的次数,当次数等于n-1时结束。
模板题链接:Kruskal算法求最小生成树
代码如下:时间复杂度O(mlogm)
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; const int N = 100000+10, M = 200000+10; struct Edge{ int u,v,w; bool operator < (const Edge &E)const { return w<E.w; } }edge[M]; int fa[N]; int n,m,cnt,ans; int find(int x) { if(fa[x]==x)return x; else return fa[x]=find(fa[x]); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)fa[i]=i; for(int i=1;i<=m;i++) { int a,b,c;scanf("%d%d%d",&a,&b,&c); edge[i].u=a;edge[i].v=b;edge[i].w=c; } sort(edge+1,edge+m+1); for(int i=1;i<=m;i++) { int u=find(edge[i].u),v=find(edge[i].v),w=edge[i].w; if(u!=v) { cnt++; fa[u]=v; ans+=w; } } if(cnt==n-1)printf("%d\n",ans); else printf("impossible\n"); return 0; }
三、Prim,Prim_heap,Kruskal算法时间复杂度比较
参考了G机器猫的博客
结论:
1.Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。
2.Prim_heap在任何时候都有令人满意的的时间复杂度,但是代价是空间消耗极大。(以及代码很复杂>_<)
但值得说一下的是,时间复杂度并不能反映出一个算法的实际优劣。
竞赛题一般给的都是稀疏图,选择Prim_heap即可;如果觉得代码量太大,想要在Prim与Kruskal算法中选一个,那就选择Kruskal算法。
标签:Prim,int,Kruskal,算法,顶点,dis 来源: https://www.cnblogs.com/myhnb/p/11244724.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。