求最小生成树的两种算法:
1.Kruskal算法(本质是贪心)
1. 把图中的所有边按代价从小到大排序;
2.按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
这种做法可以通过并查集来维护连通性,总体复杂度是O(m+并查集的玄学复杂度);
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> using namespace std; int n,m,i,j,u,v,total; struct edge{ int start,to;long long val; }bian[2000005]; int f[100000]; long long ans; int find(int x)//并查集部分 { if (f[x]==x) return x; else { f[x]=find(f[x]); return f[x]; } } bool cmp(edge a,edge b)//结构体快排时用到的 { return a.val<b.val; } inline void kruskal()//最小生成树 { for(int i=1;i<=m;i++) { u=find(bian[i].start); v=find(bian[i].to); if(u==v) continue;//判断在不在同一个并查集里面,在就下一个循环 ans+=bian[i].val;//不在,就加上 f[u]=v;//连接两个并查集 total++; if(total==n-1) break;//当形成了最小生成树后,退出(之后做的也没用了) } } int main() { scanf("%d%d",&n,&m); for(i=1;i<=n;i++) f[i]=i; for(i=1;i<=m;i++) { scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val); } sort(bian+1,bian+m+1,cmp);//快排边长 kruskal(); printf("%d",ans); return 0; }
2.Prim算法(本质还是贪心)
每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
时间复杂度是O(n^n);
但注意,每次寻找最短的一条边的时候,我们可以用堆来快速维护,使复杂度降至O((n+m)logm);
#include<cstdio> #include<queue> #include<cstring> #include<algorithm> #define R register int using namespace std; int k,n,m,cnt,sum,ai,bi,ci,head[5005],dis[5005],vis[5005]; struct Edge { int v,w,next; }e[400005]; void add(int u,int v,int w) { e[++k].v=v; e[k].w=w; e[k].next=head[u]; head[u]=k; } typedef pair <int,int> pii; priority_queue <pii,vector<pii>,greater<pii> > q; void prim() { dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()&&cnt<n) { int d=q.top().first,u=q.top().second; q.pop(); if(vis[u]) continue; cnt++; sum+=d; vis[u]=1; for(R i=head[u];i!=-1;i=e[i].next) if(e[i].w<dis[e[i].v]) dis[e[i].v]=e[i].w,q.push(make_pair(dis[e[i].v],e[i].v)); } } int main() { memset(dis,127,sizeof(dis)); memset(head,-1,sizeof(head)); scanf("%d%d",&n,&m); for(R i=1;i<=m;i++) { scanf("%d%d%d",&ai,&bi,&ci); add(ai,bi,ci); add(bi,ai,ci); } prim(); if (cnt==n)printf("%d",sum); else printf("orz"); }
例题:洛谷P1265 公路修建
#include <bits/stdc++.h> using namespace std; double x[5005],y[5005]; double dis[5005],ans; bool v[5005]; double query(double x,double x1,double y,double y1) { return (x-x1)*(x-x1)+(y-y1)*(y-y1); } int main() { int n; scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%lf%lf",&x[i],&y[i]); memset(dis,0x7f,sizeof(dis)); dis[1]=0; for(int i=1;i<=n;i++){ int k=0; for (int j=1; j<=n; j++) if(!v[j]&&dis[j]<dis[k]) k=j; v[k]=1; for (int j=1; j<=n; j++) if(!v[j]&&query(x[k],x[j],y[k],y[j])<dis[j]) dis[j]=query(x[k],x[j],y[k],y[j]); } for(int i=1;i<=n;i++) ans+=sqrt(dis[i]); printf ("%.2lf ",ans); return 0; }
从策略上来说,Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的~
所以说,Kruskal在算法效率上是比Prim快的,因为Kruskal只需一次对权重的排序就能找到最小生成树,而Prim算法需要多次对邻边排序才能找到~
prim:该算法的时间复杂度为O(n2)。与图中边数无关,该算法适合于稠密图。
kruskal:需要对图的边进行访问,所以克鲁斯卡尔算法的时间复杂度只和边又关系,可以证明其时间复杂度为O(eloge)。适合稀疏图
一句话来说,prim适合对于点进行运算,kruskal适合对于边进行运算;、