树:最小生成树-两种算法
先来说说什么是树。
树实际上是图的一种,当一个有N个点的无向连通图,只有N-1条边时,就是一棵树,即树中不会有环出现;所以对于一个图,删除某些环中的某条边,使得该图成为一棵树,那么这棵树就称为生成树。
而最小生成树的意思就是,给定有n个顶点的带权图G(E,V),找到一棵生成树,求该生成树的边权和。
Kruskal算法:
算法步骤:
1.构造一个有n个顶点的无边子图;
2.从原图选择边权最小的边加入该子图,直至子图成为一棵树;
3.边能加入子图的条件是,边的两个端点u,v还未连通,Kruskal算法中运用并查集的查询来询问两个顶点是否连通;
Kruskal算法的本质是,通过树的合并(不断加边,构成子树),来构建完整的生成树。
Kruskal+邻接表 模板如下:
//复杂度O(ElogE),E为边数 const int INF=10e8; const int MAXN=110; const int MAXM=MAXN*MAXN; struct edge { int u,v,cost; bool operator < (const edge &a)const { return cost<a.cost; } }; edge E[MAXN]; int couEdge; //记得初始化couEdge=0,然后进行加边操作 int fa[MAXN]; int Find(int x) { if(fa[x]==-1) //important!! return x; fa[x]=Find(fa[x]); return fa[x]; } int Kruskal(int n) { int ans=0; int cou=0; //important!! 建好子图之后,每给子图加上一条边,cou+1 int u,v,cost,t1,t2; memset(fa,-1,sizeof(fa)); //important!! sort(E,E+couEdge); for(int i=0;i<couEdge;i++) { u=E[i].u;v=E[i].v;cost=E[i].cost; t1=Find(u);t2=Find(v); //t1,t2分别代表u,v的父节点 if(t1!=t2) { ans+=cost; fa[t1]=t2; cou++; } if(cou==n-1) break; } if(cou<n-1) return -1; //表示该图不连通 return ans; } //加边操作 void add_edge(int u,int v,int cost) { E[couEdge].u=u;E[couEdge].v=v;E[couEdge].cost=cost; }
Prim算法:
前面说了,Kruskal算法是通过树的合并来构建生成树,而现在要说的Prim算法,其本质则是,从一个顶点扩展成生成树,复杂度O(N2)。
【对比:Kruskal和Prim的异同】
同:都是贪心思想;都是选择边权最小的边;
异:Kruskal是优先选择边权最小的边,Prim是从点集出发选择边权最小的边;
//当题目中边的数量比顶点多的时候,用Prim速度比较快。
Prim算法的基本步骤:
1.初始化点集 V={x};
2.找到边(u,v)满足:u∈点集V,v不∈点集V;
3.选取2.中满足条件的边中最小的一条加入生成树,并把v加入点集V,重复执行,直至原图所有的点都加入点集V,就得到了一棵最小生成树。
Tips:关于如何快速找到可以添加到生成树的边:
可以维护一个数组lowcost[i…j]记录点集V到各个顶点的最小边,即可快速找到边,且每当有新的点加入点集V时,该数组都要更新一次。
Prim+邻接矩阵 模板如下:
const int INF=10e8; const int MAXN=110; bool vis[MAXN]; int lowcost[MAXN]; int Prim(int cost[][MAXN],int n) {//输入cost[][],n;且cost[][]的初始化要注意没连通的边存INF int ans=0; int minn,k; memset(vis,0,sizeof(vis)); vis[1]=1; //从点1开始扩展,找距离1最小的边扩展 for(int i=1;i<=n;i++) lowcost[i]=cost[1][i]; //! for(int i=1;i<=n-1;i++) //循环n-1次,因为vis[1]=1 { minn=INF;k=-1; for(int j=1;j<=n;j++) if(!vis[j]&&minn>lowcost[j]) { minn=lowcost[j]; k=j; } if(k==-1) break; //不连通 ans+=minn;vis[k]=1; //再从k点开始扩展,准备更新lowcost[] for(int j=1;j<=n;j++) if(!vis[j]&&lowcost[j]>cost[k][j]) lowcost[j]=cost[k][j]; } return ans; }