最小生成树是图论问题中很基本的一个操作。常用的算法有Prim和Kruskal两种算法。本文对这两种算法稍作区别与讨论。
Prim算法是依赖于点的算法。它的基本原理是从当前点寻找一个离自己(集合)最近的点然后把这个点拉到自己家来(距离设为0),同时输出一条边,并且刷新到其他点的路径长度。俗称,刷表。
根据Prim算法的特性可以得知,它很适合于点密集的图。通常在教材中,对Prim算法进行介绍的标程都采用了邻接矩阵的储存结构。这种储存方法空间复杂度N^2,时间复杂度N^2。对于稍微稀疏一点的图,其实我们更适合采用邻接表的储存方式,可以节省空间,并在一定条件下节省时间。
Kruskal算法是依赖边的算法。基本原理是将边集数组排序,然后通过维护一个并查集来分清楚并进来的点和没并进来的点,依次按从小到大的顺序遍历边集数组,如果这条边对应的两个顶点不在一个集合内,就输出这条边,并合并这两个点。
根据Kruskal算法的特性可得,在边越少的情况下,Kruskal算法相对Prim算法的优势就越大。同时,因为边集数组便于维护,所以Kruskal在数据维护方面也较为简单,不像邻接表那样复杂。从一定意义上来说,Kruskal算法的速度与点数无关,因此对于稀疏图可以采用Kruskal算法。
那么究竟如何获得一种权衡的方法,既能应付稀疏图又能应付密集图呢?很简单,把两个算法都写到程序中,并且在行动之前判断一下点数边数的比例,如果达到某个临界值,就采用某种算法。当然,这样确实在实际行动中较为麻烦。
以下我们给出一道例题,用Prim和Kruskal算法分别解决该问题:
题目背景
农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。
题目描述
约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。
你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过100000
输入输出格式
输入格式:
第一行: 农场的个数,N(3<=N<=100)。
第二行..结尾: 后来的行包含了一个N*N的矩阵,表示每个农场之间的距离。理论上,他们是N行,每行由N个用空格分隔的数组成,实际上,他们限制在80个字符,因此,某些行会紧接着另一些行。当然,对角线将会是0,因为不会有线路从第i个农场到它本身。
输出格式:
只有一个输出,其中包含连接到每个农场的光纤的最小长度。
这题是一个完全不绕弯的最小生成树问题。我们分别给出两种算法的解决方案:
Prim
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int a[105][105],dist[105],n,ans=0;
int main()
{
memset(a,0,sizeof(a));
memset(dist,0,sizeof(dist));
cin>>n;
int i,j;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
memcpy(dist,a[1],sizeof(dist));
dist[1]=0;
for(i=2;i<=n;i++)
{
int tmp=999999,tmpi=0;
for(j=1;j<=n;j++)
if(dist[j]&&tmp>dist[j])
tmp=dist[j],
tmpi=j;
ans+=tmp;
for(j=1;j<=n;j++)
dist[j]=min(dist[j],a[tmpi][j]);
}
cout<<ans<<endl;
return 0;
}
Kruskal
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define UFS_LIMIT 10000
using namespace std;
struct Edge
{
int value;
int start;
int end;
} e[10005];
class UnionFindSet
{
public:
int father[UFS_LIMIT];
UnionFindSet()
{
for(int i=0;i<UFS_LIMIT;i++)
father[i]=i;
return;
}
int Find(int x)
{
int t=x,tt;
while(x!=father[x])
x=father[x];
while(t!=x)
{
tt=father[t];
father[t]=x;
t=tt;
}
return father[x];
}
void Union(int x,int y)
{
x=Find(x);
y=Find(y);
if(x!=y) father[x]=y;
return;
}
bool IfSame(int x,int y)
{
return Find(x)==Find(y);
}
};
int a[105][105],n,ec=0,ans=0;
Edge cedge(int value,int start,int end)
{
Edge t_edge;
t_edge.value=value;
t_edge.start=start;
t_edge.end=end;
return t_edge;
}
int cmp(const void *a,const void *b)
{
return (*(Edge*)a).value>(*(Edge*)b).value;
}
int main()
{
memset(a,0,sizeof(a));
cin>>n;
int i,j;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
cin>>a[i][j];
if(i>j) e[ec++]=cedge(a[i][j],i,j);
}
qsort(e,ec,sizeof(Edge),cmp);
UnionFindSet ufs;
for(i=0;i<ec;i++)
if(!ufs.IfSame(e[i].start,e[i].end))
ufs.Union(e[i].start,e[i].end),
ans+=e[i].value;
cout<<ans<<endl;
return 0;
}
P.S. 从我写的程序来看,Prim好像比Kruskal好写很多哎(笑)。

文章探讨了最小生成树问题中Prim和Kruskal两种算法的区别和适用场景。Prim算法适用于点密集图,常采用邻接矩阵,而Kruskal算法在边较少时更优,使用并查集维护。可以结合点数和边数比例选择合适算法。文中还举例说明了两种算法的应用。
331

被折叠的 条评论
为什么被折叠?



