最小生成树(Prim / Kruskal)
Kruskal算法(加边法)
思路:
首先对边的权值从小到大进行排序,而后遍历查看每一条边,循环以下步骤:
1)若该边两端顶点分属不同连通分量,则将此边加入,之后将其两端顶点合并为同一个连通分量;
2)若该边两端顶点已属于同一连同分量,则舍弃,继续查看下一条权值最小的边。
Code:
#include<bits/stdc++.h> using namespace std; const int MAXN=1000;//最大边数 int N,M;//N个顶点,M条边 struct node{ int u,v,w; bool operator < (const node b)const{ return w < b.w; } }G[MAXN],T[MAXN]; void Kruskal() { int f[N+5]={},vs1,vs2,Sum=0,q=0; sort(G,G+M);//对边的权从小到大排序 for(int i=0;i<=N;i++)//表示各顶点自成一个连通分量 f[i]=i; for(int i=1;i<=M;i++){ vs1=f[G[i].u];//获取边G[i]的始点所在的连通分量vs1 vs2=f[G[i].v];//获取边G[i]的终点所在的连通分量vs2 if(vs1!=vs2){//边的两个顶点分属不同的连通分量 T[q].u=G[i].u,T[q].v=G[i].v,T[q++].w=G[i].w;//储存最小生成树的每一条边 Sum+=G[i].w; for(int k=0;k<=N;k++)//合并vs1和vs2两个分量,即两个集合统一编号 if(f[k]==vs2)//集合编号vs2的都改为vs1 f[k]=vs1; } } if(q!=N-1)cout<<"该图不连通"<<endl; else{ cout<<"最小生成树的边权之和:"<<Sum<<endl; for(int i=0;i<q;i++)//输出最小生成树的每一条边 cout<<T[i].u<<' '<<T[i].v<<' '<<T[i].w<<endl; } } int main() { cin>>N>>M;//输入顶点数和边数 for(int i=1;i<=M;i++) cin>>G[i].u>>G[i].v>>G[i].w;//输入每条边的两个顶点和边权 Kruskal(); return 0; }
可用并查集判断边的两个顶点是否已属于同一连通分量:
void init(int n) { for(int i=0;i<=n;i++){ f[i]=i; } } int get(int x) { if(f[x]==x) return x; return get(f[x]); } int merge(int u,int v) { int t1,t2; t1=get(u); t2=get(v); if(t1!=t2){ f[t2]=t1; return 1; } return 0; }
Prim算法(加点法)
思路:
①需建立一个辅助数组dis维护已选取加入的点到其余各个顶点的边的最小权值。
②需建立一个标记数组标记哪个顶点已选取加入。
首先选取任意一个顶点加入,建立上述的dis数组,标记该顶点已选取加入,之后循环以下步骤n-1次:
(1)遍历dis数组找其中记录的边权值最小且未被选取的点加入,标记该点已选取,连接该边的两点;
(2)更新维护dis数组。
Code:
#include<bits/stdc++.h> using namespace std; const int MAXN=1000; const int inf=0x3f3f3f3f; int N,M,G[MAXN][MAXN]; void Init() { for(int i=0;i<=N;i++) for(int j=0;j<=N;j++) G[i][j]=inf; } void Prim() { pair<int,int>dis[N+5];//dis[i],第二个存当前其余顶点到该点i的边最小权值,第一个存当前该最小权值边与该点i相连的起点 int Sum=0,book[N+5]={}; for(int i=0;i<=N;i++)//选取任意一个顶点加入,此处选择编号1的点为第一个加入的点 dis[i]=make_pair(1,G[1][i]); dis[1].second=0; book[1]=1;//标记已选取 for(int i=1;i<=N-1;i++){ int Min=inf,k,u0,v0,w0; for(int j=0;j<=N;j++){ //cout<<dis[j].second<<' ';//输出dis数组信息 if(!book[j]&&dis[j].second<Min){//寻找当前dis数组中的最小边权dis[j].second,j则为下一个选取的点 Min=dis[j].second; k=j; } } book[k]=1;//标记已选取 u0=dis[k].first, v0=k, w0=Min; cout<<u0<<' '<<v0<<' '<<w0<<endl;//输出最小生成树的边 Sum+=Min; dis[k].second=0; for(int u=0;u<=N;u++){//维护更新dis数组 if(!book[u]&&dis[u].second>G[k][u]) dis[u]=make_pair(k,G[k][u]); } } cout<<"最小生成树的边权之和:"<<Sum<<endl; } int main() { int u,v,w; cin>>N>>M;//输入顶点数和边数 Init();//建图前将每个点之间的边初始化为无穷大 for(int i=0;i<M;i++){ cin>>u>>v>>w;//输入每条边的两个顶点和边权 G[u][v]=w,G[v][u]=w; } Prim(); return 0; }
(备注:因为有些测试用例的顶点编号为0~N-1,而有些是1~N,为了代码对两者都的适用,上述两份代码对顶点的for遍历都是从0到N,故可输入的顶点编号范围为0~N。)