最小生成树(prim算法与kruskal算法)(模板)

首先说一下什么是树:

1、只含一个根节点

2、任意两个节点之间只能有一条或者没有线相连

3、任意两个节点之间都可以通过别的节点间接相连

4、除了根节点没一个节点都只有唯一的一个父节点

5、也有可能是空树(不含任何节点)

最小生成树就是:

在所有数据满足是一棵树的情况下一条将所有节点都连接起来且长度最短的一条路(因为任意两个节点之间有权值

(相连的两点之间权值为一个具体的数,不相连的两个点之间权值为无穷大))

下面介绍通用的求最小生成树的两种算法:

ps:这里用的两种算法都是用邻接矩阵实现适合点稠密型数据或者数据较小的情况:

(1)prim算法:

<span style="font-size:12px;">/*
*  数组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);
}</span>

kruskal算法:

此算法的核心就是在并查集(并查集知识请看 知识小总结记录分类中的并查集(http://www.cnblogs.com/tonghao/p/4442821.html))的基础上对两点之间距离进行排序:

find()函数用来查找根节点

<span style="font-size:12px;">int find(int father)//查找根节点
{
    int t;
    int children=father;
    while(father!=set[father])
    father=set[father];
    while(fa!=set[children])
    {
        t=set[children];
        set[children]=father;
        children=t;
    }
    return father;
}</span>

mix函数用来合并两个节点,使两个节点的父节点相同

<span style="font-size:12px;">void mix(int x,int y)//将两个点合并(即另两点根节点相同)
{
    int fx;
    int fy;
    fx=find(x);
    fy=find(y);
    if(fx!=fy)
    set[fx]=fy;
}</span>

利用结构体排序:

<span style="font-size:12px;">struct record
{
    int begin;//记录两个点中的一个
    int end;//记录两个点中的一个
    int dis;//记录两点之间距离
}num[MAX];
bool cmp(int a,int b)
{
    return a.dis<b.dis;//对两点之间距离进行从大到小的排序
}</span>

用一个题来实现上述两个算法:

省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。

Input
测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M ( < 100 );随后的 N
行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。

Output
对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。

Sample Input
3 3
1 2 1
1 3 2
2 3 4
1 3
2 3 2
0 100

Sample Output
3
?

prim算法:

<span style="font-size:12px;">#include<stdio.h>
#include<string.h>
#define INF 0x3f3f3f
int lowcost[110];//此数组用来记录第j个节点到其余节点最少花费 
int map[110][110];//用来记录第i个节点到其余n-1个节点的距离 
int visit[110];//用来记录最小生成树中的节点 
int city;
void prime()
{
    int min,i,j,next,mincost=0;
    memset(visit,0,sizeof(visit));//给最小生成树数组清零 
    for(i=1;i<=city;i++)
    {
        lowcost[i]=map[1][i];//初始化lowcost数组为第1个节点到剩下所有节点的距离 
    }
    visit[1]=1;//选择第一个点为最小生成树的起点 
    for(i=1;i<city;i++)
    {
        min=INF;
        for(j=1;j<=city;j++)
        {
            if(!visit[j]&&min>lowcost[j])//如果第j个点不是最小生成树中的点并且其花费小于min 
            {
                min=lowcost[j];
                next=j;//记录下此时最小的位置节点 
            }
        }
        if(min==INF)
        {
            printf("?\n");
            return ;
        }
        mincost+=min;//将最小生成树中所有权值相加 
        visit[next]=1;//next点加入最小生成树 
        for(j=1;j<=city;j++)
        {
            if(!visit[j]&&lowcost[j]>map[next][j])//如果第j点不是最小生成树中的点并且此点处权值大于第next点到j点的权值 
            {
                lowcost[j]=map[next][j];         //更新lowcost数组 
            }
        }
    }
    printf("%d\n",mincost);
}
int main()
{
    int road;
    int j,i,x,y,c;
    while(scanf("%d%d",&road,&city)&&road!=0)
    {
        memset(map,INF,sizeof(map));//初始化数组map为无穷大 
        while(road--)
        {
            scanf("%d%d%d",&x,&y,&c);
            map[x][y]=map[y][x]=c;//城市x到y的花费==城市y到想的花费 
        }
        prime();
    }
    return 0;
}</span>

kruskal算法:

/*
*  set[]数组用来存放根节点
*  find()函数用来查找根节点
*  mix()函数用来将两个点合并(即使其父节点相同)
*/
#include<stdio.h>
#include<algorithm>
using namespace std;
int set[110];
struct record
{
    int beg;
    int end;
    int money;
}s[11000];
int find(int fa) //查找根节点
{
    int ch=fa;
    int t;
    while(fa!=set[fa])
    fa=set[fa];
    while(ch!=fa)
    {
        t=set[ch];
        set[ch]=fa;
        ch=t;
    }
    return fa;
}
void mix(int x,int y) //合并两点
{
    int fx,fy;
    fx=find(x);
    fy=find(y);
    if(fx!=fy)
    set[fx]=fy;
}
bool cmp(record a,record b)
{
    return a.money<b.money; //对价格进行排序
}
int main()
{
    int city,road,n,m,j,i,sum;
    while(scanf("%d",&road)&&road!=0)
    {
        scanf("%d",&city);
        for(i=0;i<road;i++)
        {
            scanf("%d%d%d",&s[i].beg,&s[i].end,&s[i].money);
        }
        for(i=1;i<=city;i++)
        set[i]=i; //初始化set[]数组
        sort(s,s+road,cmp);
        sum=0;
        for(i=0;i<road;i++) //这个地方可能不好理解  我在下面作出解释
        {
            if(find(s[i].beg)!=find(s[i].end)) //当两点没有连接时才可以连接两点
            {
                mix(s[i].beg,s[i].end); //因为已经按照价钱从高到低排序好了所以可以找到
                sum+=s[i].money; //在两个点 未相连的情况下的最小权值并将其相连
            }
        }  
        j=0;
        for(i=1;i<=city;i++)
        {
            if(set[i]==i) 
            j++;
            if(j>1)
            break;
        }
        if(j>1) //如果根节点不只一个则证明此组数据不是一棵树
        printf("?\n");
        else
        printf("%d\n",sum);
    }
    return 0;
}

这里对上面代码中所说的地方作出解释:

<span style="font-size:12px;">/*
*例如给出一组数据
*3 3
*1 2 1
*2 3 4
*1 3 2
*我们按照价钱递减的顺序排号之后就是
*1 2 1
*1 3 2
*2 3 4
*通过查找根节点我们首先是对1 2进行根节点查找即
*find(1) , find(2)可知find(1)==1,find(2)==2 不相等
*所以合并两点的根节点即find(2)==1;同理find(3)==1
*因为2,3都已经合并所以第三组数据2 3 4的价格4就不会
* 加到最小树的结果中去
*/
for(i=0;i<road;i++) <br>{
    if(find(s[i].beg)!=find(s[i].end)) //当两点没有连接时才可以连接两点
    {
        mix(s[i].beg,s[i].end); //因为已经按照价钱从高到低排序好了所以可以找到
        sum+=s[i].money; //在两个点 未相连的情况下的最小权值并将其相连
    }
} </span>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值