对于一个边上具有权值的图来说,其边权值和最小的生成树叫做图G的最小生成树
求无向图最小生成树主要有prim和kruskal两种算法
1.prim
将点集V分成Va和Vb两部分,Va为已经连入生成树的点,Vb为没有连入的点,按照边的大小逐渐向Va中加点,直到Va中包含所有点,具体步骤,复杂度O(mlogn)
⑴.首先初始化生成树的权值为0,任选一点放入Va,其余点放入Vb
⑵.在Vb中找一点u,在Va中找一点v(其实v一直不变),使得uv间距离最短,并更新u所连边,这也就是为什么v不变的原因
⑶.重复步骤2,直到Vb中没有点为止
#include <queue> #include <vector> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <iostream> #include <algorithm> const double INF=0x3f3f3f3f; using namespace std; double dis[1005],G[1005][1005]; int n,x[1005],y[1005],vis[1005]; double prim(int v){ int i,j,u; double sum,tmp; sum=0; memset(vis,0,sizeof(vis)); for(i=1;i<=n;i++) dis[i]=G[v][i]; vis[v]=1; for(i=1;i<n;i++){ u=v; tmp=INF; for(j=1;j<=n;j++) if(dis[j]<tmp&&vis[j]==0){ tmp=dis[j]; u=j; } //找出与v相连最小的边 sum+=tmp; vis[u]=1; for(j=1;j<=n;j++) if(!vis[j]){ if(dis[j]>G[u][j]) dis[j]=G[u][j]; } //更新与u相连的边的权值 } return sum; } int main(){ int i,j; double ans; scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]); for(i=1;i<=n;i++) for(j=1;j<=n;j++) G[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])); ans=prim(1); //通过每个点的坐标算出每个点间距离 printf("%.2lf\n",ans); return 0; }2. kruskal
基于贪心的思想逐渐加入边并判断是否形成环,复杂度O(mlogm)
⑴.初始化,并将E进行排序
⑵.不断将边加入图中,并判断是否形成环
⑶.判断选择的边是否是n-1,并计算步骤2的权值和
#include <queue> #include <vector> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <iostream> #include <algorithm> using namespace std; int V,E; int par[505],ran[505],vis[505]; void init(int n){ int i; for(i=0;i<=n;i++){ ran[i]=0; par[i]=i; } } int find(int x){ if(par[x]==x) return x; return par[x]=find(par[x]); } void unite(int x,int y){ x=find(x); y=find(y); if(x==y) return; if(ran[x]<ran[y]) par[x]=y; else{ par[y]=x; if(ran[x]==ran[y]) ran[x]++; } } bool same(int x,int y){ return find(x)==find(y); } //并查集判断是否有环 struct node{ int u,v,cost; }; bool cmp(node a,node b){ return a.cost<b.cost; } node es[505]; int kruskal(){ int i; int res=0; init(V); sort(es,es+E,cmp); for(i=0;i<E;i++){ node e=es[i]; if(!same(e.u,e.v)){ unite(e.u,e.v); res+=e.cost; } } return res; } int main(){ int i; scanf("%d%d",&V,&E); memset(vis,0,sizeof(vis)); for(i=0;i<E;i++) scanf("%d%d%d",&es[i].u,&es[i].v,&es[i].cost); printf("%d\n",kruskal()); return 0; }
3.次小生成树
基于最小生成树的算法演变出次小生成树,其实基本的思想就是连入一条不在最小生成树上的边,从而形成一个环,去掉在环中并且在最小生成树上最大的边,遍历所有不在最小生成树上的边并进行同样的操作最小值即为次小生成树,简单证明就是连入一条边后去掉一个最大值相当于比原来的值增加的值最小(增加量=添加的边-环上的某一条边(并且这条边在最小生成树上),添加的边的权值一定,因此使环上的边最大),因次去掉最大的边,kruskal的复杂度是O(mlogm),求出环上的最大值复杂度是O(n*n),因此次小生成树的复杂度是O(mlogm+n*n)
#include <math.h> #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> const double INF=0x3f3f3f3f; using namespace std; int dis[105],path[105][105],G[105][105]; int n,m,pre[105],vis[105],used[105][105]; int prim(int v){ int i,j,u,sum,tmp; sum=0; memset(vis,0,sizeof(vis)); memset(used,0,sizeof(used)); memset(path,0,sizeof(path)); for(i=1;i<=n;i++){ dis[i]=G[v][i]; pre[i]=1; } vis[v]=1; for(i=1;i<n;i++){ u=v; tmp=INF; for(j=1;j<=n;j++) if(dis[j]<tmp&&vis[j]==0){ tmp=dis[j]; u=j; } sum+=tmp; vis[u]=1; used[u][pre[u]]=used[pre[u]][u]=1; for(j=1;j<=n;j++){ if(vis[j]&&j!=u) //从j到父节点上的边的最大值和最小生成树上的边之间求最大值 path[u][j]=path[j][u]=max(path[j][pre[u]],dis[u]); if(!vis[j]){ //与dp有些类似 if(dis[j]>G[u][j]){ dis[j]=G[u][j]; pre[j]=u; } } } } return sum; } int second_prim(int tmp){ int i,j,ans; ans=INF; for(i=1;i<=n;i++) for(j=1;j<=n;j++){ if(i!=j&&used[i][j]==0) ans=min(ans,tmp+G[i][j]-path[i][j]); } //遍历每条边求次小生成树 return ans; } int main(){ int i,j,x,y,z,ans,tmp; scanf("%d%d",&n,&m); memset(G,INF,sizeof(G)); for(i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&z); G[x][y]=G[y][x]=z; } tmp=prim(1); //先求出最小生成树 ans=second_prim(tmp); printf("%d\n",ans); return 0; }