最小生成树Prim与Kruskal算法的比较

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

最小生成树是图论问题中很基本的一个操作。常用的算法有Prim和Kruskal两种算法。本文对这两种算法稍作区别与讨论。


Prim算法是依赖于点的算法。它的基本原理是从当前点寻找一个离自己(集合)最近的点然后把这个点拉到自己家来(距离设为0),同时输出一条边,并且刷新到其他点的路径长度。俗称,刷表。
根据Prim算法的特性可以得知,它很适合于点密集的图。通常在教材中,对Prim算法进行介绍的标程都采用了邻接矩阵的储存结构。这种储存方法空间复杂度N^2,时间复杂度N^2。对于稍微稀疏一点的图,其实我们更适合采用邻接表的储存方式,可以节省空间,并在一定条件下节省时间。

Kruskal算法是依赖边的算法。基本原理是将边集数组排序,然后通过维护一个并查集来分清楚并进来的点和没并进来的点,依次按从小到大的顺序遍历边集数组,如果这条边对应的两个顶点不在一个集合内,就输出这条边,并合并这两个点。
根据Kruskal算法的特性可得,在边越少的情况下,Kruskal算法相对Prim算法的优势就越大。同时,因为边集数组便于维护,所以Kruskal在数据维护方面也较为简单,不像邻接表那样复杂。从一定意义上来说,Kruskal算法的速度与点数无关,因此对于稀疏图可以采用Kruskal算法。

那么究竟如何获得一种权衡的方法,既能应付稀疏图又能应付密集图呢?很简单,把两个算法都写到程序中,并且在行动之前判断一下点数边数的比例,如果达到某个临界值,就采用某种算法。当然,这样确实在实际行动中较为麻烦。



以下我们给出一道例题,用Prim和Kruskal算法分别解决该问题:

题目背景

农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。

题目描述

约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。

你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过100000

输入输出格式

输入格式:
第一行: 农场的个数,N3<=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好写很多哎(笑)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值