最小生成树~~prim算法和kruskal算法~~模板

1、什么是最小生成树

现在假设有一个很实际的问题:我们要在n个城市中建立一个通信网络,则连通这n个城市需要布置n-1一条通信线路,这个时候我们需要考虑如何在成本最低的情况下建立这个通信网? 
于是我们就可以引入连通图来解决我们遇到的问题,n个城市就是图上的n个顶点,然后,边表示两个城市的通信线路,每条边上的权重就是我们搭建这条线路所需要的成本,所以现在我们有n个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个通信网,当我们构造这个连通网所花的成本最小时,搭建该连通网的生成树,就称为最小生成树。

构造最小生成树有很多算法,但是他们都是利用了最小生成树的同一种性质:MST性质(假设N=(V,{E})是一个连通网,U是顶点集V的一个非空子集,如果(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必定存在一颗包含边(u,v)的最小生成树),下面就介绍两种使用MST性质生成最小生成树的算法:普里姆算法和克鲁斯卡尔算法。


2、普里姆算法—Prim算法

算法思路: 
首先就是从图中的一个起点a开始,把a加入U集合,然后,寻找从与a有关联的边中,权重最小的那条边并且该边的终点b在顶点集合:(V-U)中,我们也把b加入到集合U中,并且输出边(a,b)的信息,这样我们的集合U就有:{a,b},然后,我们寻找与a关联和b关联的边中,权重最小的那条边并且该边的终点在集合:(V-U)中,我们把c加入到集合U中,并且输出对应的那条边的信息,这样我们的集合U就有:{a,b,c}这三个元素了,一次类推,直到所有顶点都加入到了集合U。

下面我们对下面这幅图求其最小生成树:

这里写图片描述

假设我们从顶点v1开始,所以我们可以发现(v1,v3)边的权重最小,所以第一个输出的边就是:v1—v3=1: 
这里写图片描述

然后,我们要从v1和v3作为起点的边中寻找权重最小的边,首先了(v1,v3)已经访问过了,所以我们从其他边中寻找,发现(v3,v6)这条边最小,所以输出边就是:v3—-v6=4 
这里写图片描述

然后,我们要从v1、v3、v6这三个点相关联的边中寻找一条权重最小的边,我们可以发现边(v6,v4)权重最小,所以输出边就是:v6—-v4=2. 
这里写图片描述

然后,我们就从v1、v3、v6、v4这四个顶点相关联的边中寻找权重最小的边,发现边(v3,v2)的权重最小,所以输出边:v3—–v2=5 
这里写图片描述

然后,我们就从v1、v3、v6、v4,v2这2五个顶点相关联的边中寻找权重最小的边,发现边(v2,v5)的权重最小,所以输出边:v2—–v5=3 
这里写图片描述

最后,我们发现六个点都已经加入到集合U了,我们的最小生成树建立完成。

/*
*  数组tree[]用来记录最小生成树的节点
*  数组lowdis[]记录从起点到其余所有点的距离并不断更新
*  数组map[][]记录所有数据两点之间的距离
*  point是所有节点的数目,begin是起点
*  mindis是最小生成树的长度
*/
void prime()
{
    int i,j,min,mindis=0,next;
    memset(tree,0,sizeof(tree));
    for(i=1;i<=point;i++)
    {
        lowdis[i]=map[begin][i];//用lowdis[]数组记录下从起点到剩下所有点的距离
    }
    tree[begin]=1;//标记起点(即最小生成树中的点)
    for(i=1;i<point;i++)
    {
        min=INF;
        for(j=1;j<=point;j++)
        {
            if(!tree[j]&&min>lowdis[j])
            {
                min=lowdis[j];//求出从当前起点到其余所有点的距离中最短的
                next=j;
            }
        }
        mindis+=min;//记录下整条最小树的长度
        tree[next]=1;
        for(j=1;j<=point;j++)
        {
            if(!tree[j]&&lowdis[j]>map[next][j])
            lowdis[j]=map[next][j];//更新lowdis[]数组
        }
    }
    printf("%d\n",mindis);
}

3、克鲁斯卡算法

算法思路: 
(1)将图中的所有边都去掉。 
(2)将边按权值从小到大的顺序添加到图中,保证添加的过程中不会形成环 
(3)重复上一步直到连接所有顶点,此时就生成了最小生成树。这是一种贪心策略。

这里同样我们给出一个和Prim算法讲解中同样的例子,模拟克鲁斯卡算法生成最小生成树的详细的过程:

首先完整的图如下图: 
这里写图片描述

然后,我们需要从这些边中找出权重最小的那条边,可以发现边(v1,v3)这条边的权重是最小的,所以我们输出边:v1—-v3=1 
这里写图片描述

然后,我们需要在剩余的边中,再次寻找一条权重最小的边,可以发现边(v4,v6)这条边的权重最小,所以输出边:v4—v6=2 
这里写图片描述

然后,我们再次从剩余边中寻找权重最小的边,发现边(v2,v5)的权重最小,所以可以输出边:v2—-v5=3, 
这里写图片描述

然后,我们使用同样的方式找出了权重最小的边:(v3,v6),所以我们输出边:v3—-v6=4 
这里写图片描述

好了,现在我们还需要找出最后一条边就可以构造出一颗最小生成树,但是这个时候我们有三个选择:(v1,V4),(v2,v3),(v3,v4),这三条边的权重都是5,首先我们如果选(v1,v4)的话,得到的图如下: 
这里写图片描述 
我们发现,这肯定是不符合我们算法要求的,因为它出现了一个环,所以我们再使用第二个(v2,v3)试试,得到图形如下: 
这里写图片描述

我们发现,这个图中没有环出现,而且把所有的顶点都加入到了这颗树上了,所以(v2,v3)就是我们所需要的边,所以最后一个输出的边就是:v2—-v3=5

OK,到这里,我们已经把克鲁斯卡算法过了一遍,下面我们就用具体的代码实现它:

//这道题的母题是HDU-1233
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int father[100];
int n,m;
struct point
{
    int u;
    int v;
    int w;
}a[5000];
bool comp(point a1,point a2) /*按权值从小到大排序*/
{
    return a1.w<a2.w;
}
void initial() /*并查集初始化*/
{
    for(int i=0;i<=100;i++)
        father[i]=i;
}
int find(int x)  /*查找根节点*/
{
    if(father[x]==x)
        return x;
    return find(father[x]);
}
void merge(int p,int q)  /*合并两个集合*/
{
    int pp=find(p);
    int qq=find(q);
    if(pp!=qq)
    {
        if(pp<qq)
            father[qq]=pp;
        else
            father[pp]=qq;
    }
}
int kruskal()
{
    initial();  /*初始化*/
    int ans=0;
    sort(a+1,a+m+1,comp); /*排序*/
    for(int i=1;i<=m;i++)
    {
        int x=find(a[i].u);
        int y=find(a[i].v);
        if(x!=y)  /*两端点不属于同一集合*/
        {
            ans+=a[i].w;
            merge(x,y); /*合并*/
        }
    }
    return ans;
}
int main()
{
    int i,sum;
    while(~scanf("%d",&n)&&n!=0)
    {
        m=n*(n-1)/2;
        for(i=1;i<=m;i++)
            scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);
        sum=kruskal();
        printf("%d\n",sum);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值