算法竞赛模板 最小生成树
①克鲁斯卡尔算法(Kruskal)
n是顶点数,m是边数;
cnt是已连的边数,如果已连的边数=总点数-1,即可跳出。
#include<bits/stdc++.h> #define MAX 1005 using namespace std; int p[MAX],n,m; struct edge{ int x,y,w; }a[MAX]; int find(int r) { if(p[r]!=r) p[r]=find(p[r]); return p[r]; } void join(int x,int y) { int fx=find(x),fy=find(y); if(fx!=fy) p[fx]=fy; } void init() { for(int i=0;i<=MAX;i++) p[i]=i; } bool cmp(edge a,edge b) { return a.w<b.w; } int kruskal() { sort(a,a+m,cmp); int cnt=0,cost=0,i; for(i=0;i<m;i++) { int fx=find(a[i].x),fy=find(a[i].y); if(fx!=fy) { p[fx]=fy; cost+=a[i].w; cnt++; } if(cnt==n-1)break; } return cost; } int main() { int d,i; while(cin>>n>>m) { init(); for(i=0;i<m;i++) { scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].w,&d); if(d==1)//将已有的路连起来 join(a[i].x,a[i].y); } cout<<kruskal()<<endl; } return 0; }
②普里姆算法(Prim)
(1) n是顶点数,m是边数;
(2) edge[i][j]表示边e=(i,j)的权值,不存在的情况下设为INF,所以要记得提前做赋值操作;
(3) mincost[i]数组表示已访问点的集合到每个未访问点 i 的最小权值;
(4) vis[i]表示顶点 i 是否已访问
<1> 基础prim算法
#include<bits/stdc++.h> #define MAX 1005 #define INF 0x3f3f3f3f using namespace std; int n,m,edge[MAX][MAX],mincost[MAX],vis[MAX]; void init() { int u,v,w,i; memset(edge,INF,sizeof(edge)); for(i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); edge[u][v]=edge[v][u]=w; } for(i=1;i<=n;i++) { vis[i]=0; mincost[i]=INF; } mincost[1]=0; } int prim() { int i,v,cost=0; init(); while(1) { v=-1; for(i=1;i<=n;i++) if(!vis[i]&&(v==-1||mincost[i]<mincost[v])) v=i; if(v==-1) break; vis[v]=1; cost+=mincost[v]; for(i=1;i<=n;i++) mincost[i]=min(mincost[i],edge[i][v]); } return cost; } int main() { cin>>n>>m; cout<<prim()<<endl; return 0; }
<2> prim之记录并输出每次加入的边
#include<bits/stdc++.h> #define MAX 1005 #define INF 0x3f3f3f3f using namespace std; int n,m,edge[MAX][MAX],mincost[MAX],vis[MAX]; void init() { int u,v,w,i; memset(edge,INF,sizeof(edge)); for(i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); edge[u][v]=edge[v][u]=w; } for(i=1;i<=n;i++) { vis[i]=0; mincost[i]=INF; } mincost[1]=0; } void prim() { int i,v,start,end,sum=0,minn; init(); while(1) { v=-1; for(i=1;i<=n;i++) if(!vis[i]&&(v==-1||mincost[i]<mincost[v])) v=i; if(v==-1) break; vis[v]=1,sum++; minn=INF; for(i=1;i<=n;i++) { mincost[i]=min(mincost[i],edge[i][v]); if(minn>mincost[i]&&!vis[i]) { minn=mincost[i]; end=i; } } if(sum==n) continue; minn=INF; for(i=n;i>=1;i--) { if(minn>=edge[i][end]&&vis[i]) { minn=edge[i][end]; start=i; } } printf("%d %d %d\n",start,end,mincost[end]); } } int main() { cin>>n>>m; prim(); return 0; }
#include<bits/stdc++.h> #define MAX 1005 #define INF 0x3f3f3f3f using namespace std; int n,m,edge[MAX][MAX],mincost[MAX],vis[MAX]; void init() { int u,v,w,i; memset(edge,INF,sizeof(edge)); for(i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); edge[u][v]=edge[v][u]=w; } for(i=1;i<=n;i++) { vis[i]=0; mincost[i]=INF; } mincost[1]=0; //必须先确保下标的这个点1是存在的! } int prim() { int i,v,cost=0; init(); while(1) { /*--------------------循环A---------------------*/ /* PS:点v是距已访问点集合最近的未访问点 1、若点i是进循环A以来,第一个未被访问过的点,则无条件保存到点v 2、若点i不是第一个未被访问过的点,但点i距离已访问点集合更近,则保存到点v */ v=-1; for(i=1;i<=n;i++) if(!vis[i]&&(v==-1||mincost[i]<mincost[v])) v=i; /*--------------------循环A---------------------*/ //如果所有点都被访问过了,v还是-1,则会跳出这个while循环 if(v==-1) break; //将访问到的最近点v标记为已访问状态 vis[v]=1; //总距离增加 cost+=mincost[v]; /*--------------------循环B---------------------*/ /* PS:mincost[i]是已访问点集合到每个未访问点i的最小权值 1、若新增的点v距离某个未访问点i相较于之前更近,则将 已访问点集合到i的距离 更新为edge[v][i] 2、否则保持原样 */ for(i=1;i<=n;i++) mincost[i]=min(mincost[i],edge[v][i]); /*--------------------循环B---------------------*/ } return cost; } int main() { cin>>n>>m; cout<<prim()<<endl; return 0; }
③ 总结
Kruskal在所有边中不断寻找最小的边,Prim在U和V两个集合之间寻找权值最小的连接。
它们的共同点在于:构造过程中都不能形成环!
(1) 时间上:
Prim适合稠密图,复杂度为O(n*n),因此通常使用邻接矩阵储存;
Kruskal适合稀疏图,多用邻接表储存,复杂度为O(e*loge)。
(2) 空间上:
Prim适合边较多的情况;
Kruskal适合点较多的情况。