最小生成树(或称为最小代价生成树)
对无向连通图的生成树,各边的权值总和称为生成树的权,权最小的生成树称为最小生成树。
构成最小生成树的三条准则:
1.必须只使用该网络中的边来构造最小生成树
2.必须使用且仅使用n-1条边来联结网络中的n个顶点
3.不能使用产生回路的边
常用的构造最小生成树算法:克鲁斯卡尔(Kruskal)算法,普里姆(Prim)算法
MST算法对比分析
算法 | 最坏情况下时间复杂度 | 特点 |
---|---|---|
Prim算法 | O(n^2) | 适合于稠密图 |
Kruskal算法 | O(elge) | 排序时间开销占主导地位 |
克鲁斯卡尔算法
算法思想:
以边为主导地位,始终都是选择当前可用的最小权值的边。
实现步骤:
从网络中选择权值最小的边,将所有边的信息存放到一个结构体中,并将结构体数组按照权值从大到小排序,依次选用该网络的边。
选择权值最小的边后,要判断两个顶点是否属于同一个连通分量,如果是,则要舍去,不是,则选用,并将这两个连通分量合并为一个连通分量。(并查集实现)
Constructing Roads
http://poj.org/problem?id=2421
以上题为例
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[10010][10010];
struct node
{
int u,v,w;
} e[100010];//结构体存每个点与边之间的权值
int par[100010];
int n,q,d,i,j,t,sum;
bool cmp(node x,node y)
{
return x.w<y.w;//排序以便后续每次找最小的权值
}
void init()
{
for(int i=1; i<=n; i++)
par[i]=i;//初始化
}
int getf(int v)//找根节点
{
if(par[v]==v)
return v;
else
return par[v]=getf(par[v]);
}
void merge(int u,int v,int w)//合并两个连通分量为一个连通分量
{
int t1,t2;
t1=getf(u);
t2=getf(v);
if(t1!=t2)
{
par[t2]=t1;
t++;//已连的边的个数
sum+=w;//加权
}
}
int main()
{
scanf("%d",&n);
init();
int k=0;
for(i=1; i<=n; i++)
{
for(j=1; j<=n; j++)
{
scanf("%d",&d);
if(i<j)
{
e[k].u=i;
e[k].v=j;
e[k++].w=d;
}
}
}
scanf("%d",&q);
for(int i=1; i<=q; i++)
{
int x,y;
scanf("%d %d",&x,&y);
merge(x,y,0);
}
t=0;
sum=0;
sort(e,e+k,cmp);
for(int i=0; i<k; i++)
{
if(t==n-1)//当连的边等于n-1,说明所有点已连接
break;
merge(e[i].u,e[i].v,e[i].w);
}
printf("%d\n",sum);
return 0;
}
普里姆算法
算法思想:
以顶点为主导地位,从起始顶点出发,通过选择当前可用的最小权值边依次把其他顶点加入到生成树当中来。
具体过程:
1.从无向连通图中选择一个起始顶点u0,首先将它加入到集合T中,然后选择与u0关联的,具有最小权值的边(u0,v),将顶点v加入到顶点集合T中。
- 每一次都找与上一个点有关联且具有最小权值的边加入,如此持续下去,直到网络中的所有顶点都加入到生成树顶点集合T中为止。
Building a Space Station
http://poj.org/problem?id=2031
以这个题为例:
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
double dis[110];
double x[110],y[110],z[110],r[110];
double mp[110][110];
int book[110];
const double inf=0x3f3f3f3f3f;
int main()
{
int n;
while(~scanf("%d",&n)&&n)
{
if(n==0)
break;
memset(mp,0,sizeof(mp));
//double x,y,z,r;
double R;
for(int i=1; i<=n; i++)
scanf("%lf %lf %lf %lf",&x[i],&y[i],&z[i],&r[i]);
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
double t=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])+(z[i]-z[j])*(z[i]-z[j]));
R=r[i]+r[j];
if(t>R)
mp[i][j]=mp[j][i]=t-R;
else
mp[i][j]=mp[j][i]=0;
}
}
memset(book,0,sizeof(book));
for(int i=1; i<=n; i++)
dis[i]=mp[1][i];//初始化
book[1]=1;
double ans=0.0,minn;
int u;
//prim算法模板
for(int i=1;i<n;i++)
{
minn=inf;
for(int j=1;j<=n;j++)
{
if(!book[j]&&dis[j]<minn)
{
minn=dis[j];
u=j;//选取一个顶点
}
}
book[u]=1;
ans+=dis[u];
for(int v=1;v<=n;v++)
{
if(!book[v]&&dis[v]>mp[u][v])//每次找与上一个顶点相关的且权值最小的
{
dis[v]=mp[u][v];
}
}
}
printf("%.3f\n",ans);
}
return 0;
}