关于生成树和最小生成树:
- 生成树:一个连通图的生成树是一个极小连通子图,其中含有图中的全部顶点,和构成一棵树的(n-1)条边。一棵有n个顶点的生成树(连通无回路图)有且仅有(n-1)条边。
- 最小生成树:对于一个带权(假设每条边上的权均为大于0的实数)连通无向图G中的不同生成树,其每棵树的所有边上的和也可能不同,图的所有生成树中具有边上的权值之和最小的树称为最小生成树。
例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
这里介绍最小生成树两种方法:Prim和Kruskal。
- Prim算法:
假设V是图中顶点的集合,E是图中边的集合,TE为最小生成树中的边的集合,则prim算法通过以下步骤可以得到最小生成树:
1:初始化:U={u 0},TE={f}。此步骤设立一个只有结点u 0的结点集U和一个空的边集TE作为最小生成树的初始形态,在随后的算法执行中,这个形态会不断的发生变化,直到得到最小生成树为止。
2:在所有u∈U,v∈V-U的边(u,v)∈E中,找一条权最小的边(u 0,v 0),将此边加进集合TE中,并将此边的非U中顶点加入U中。此步骤的功能是在边集E中找一条边,要求这条边满足以下条件:首先边的两个顶点要分别在顶点集合U和V-U中,其次边的权要最小。找到这条边以后,把这条边放到边集TE中,并把这条边上不在U中的那个顶点加入到U中。这一步骤在算法中应执行多次,每执行一次,集合TE和U都将发生变化,分别增加一条边和一个顶点,因此,TE和U是两个动态的集合,这一点在理解算法时要密切注意。
3:如果U=V,则算法结束;否则重复步骤2。可以把本步骤看成循环终止条件。我们可以算出当U=V时,步骤2共执行了n-1次(设n为图中顶点的数目),TE中也增加了n-1条边,这n-1条边就是需要求出的最小生成树的边。
-
Kruskal算法:假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
两者的区别:Prim是以更新过的节点的连边找最小值,Kruskal是直接将边排序;Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。两者都运用了贪心的思路。
下面推荐两个最小生成树的模板题,供大家参考。
题意:裸的求最小生成树,采用了Prim+优先队列AC。
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define R register int
using namespace std;
int k,n,m,cnt,sum,head[1000],dis[1000],vis[1000];
int mp[110][110];
typedef pair <int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> > q;
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;
}
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()
{
int i,j;
while(scanf("%d",&n)!=EOF)
{
memset(dis,127,sizeof(dis));
memset(head,-1,sizeof(head));
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
scanf("%d",&mp[i][j]);
if(i!=j)
{
add(i,j,mp[i][j]);
}
}
}
prim();
if (cnt==n)
printf("%d\n",sum);
}
return 0;
}
题意:求最小生成树中最大的边的权值,采用Kruskal+并查集优化。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define N 510
struct node{
int x,y,v;
}e[N*N];
int t,fa[N];
bool cmp(const node &a,const node &b){
return a.v<b.v;
}
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main(){
scanf("%d",&t);
while(t--){
int n,cnt=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int x;
scanf("%d",&x);
if(x){
e[++cnt].x=i;e[cnt].y=j;e[cnt].v=x;
}
}
}
int k=0,res=0;
sort(e+1,e+cnt+1,cmp);
for(int i=1;i<=cnt;i++){
int fx=find(e[i].x),fy=find(e[i].y);
if(fx!=fy){
fa[fy]=fx;
k++;
}
if(k==n-1){
res=e[i].v;
break;
}
}
printf("%d\n",res);
}
return 0;
}
上面两个题为最小生成树的基础题,刚接触最小生成树可以拿这两个题目来练练手,祝大家早日AC。