最小生成树算法

前言:

最小生成树:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。

                                                    

 

假设上面给的图转换成现实例子:

上面图中的椭圆代表着一个城镇,椭圆中的数字代表城镇的编号。每两个城镇之间的横线代表着这两个城镇的路,横线上的数字代表着这条路的长度。

问你,想在这些城镇之间修建道路,怎样修建才能使所有的城镇两两连通,并且修路的距离最短?

 给出数据:

6 9

2 4 11

3 5 13

4 6 3

5 6 4

2 3 6

4 5 7

1 2 1

3 4 9

1 3 2

把给出的数据变成关系矩阵:

                                             

思路:

1.prim算法

把城镇分成两个集合:E()代表这个集合中的城镇可以连通;V()代表这个集合中的城镇还未连通;

我们随便选取一个城镇,加入集合E(),然后每一次都是在集合V()中选取一个离集合E()中的任意一个城镇的距离最近的城镇加入集合E()中;一直到把所有的城镇都连通(即进行N-1);

                                  

以上面的图为例子:

最开始两个集合的情况是:E(),V(1,2,3,4,5,6)

(1),我们首先选择1号城镇加入集合E ;   {E(1),V(2,3,4,5,6)}

(2)然后和1号城镇相连接的最近的城镇是2号城镇,所以连接2号城镇;{E(1,2),V(3,4,5,6)}

(3)接下来,集合V中和集合E中相连的最小值是1号和2号,所以连接3号城镇;{E(1,2,3),V(4,5,6)}

(4)下面集合V中和集合E中相连的最小值是2号和3号,但是2和3已经连通,所以选择下一个3和4,所以连接4号城镇;

         {E(1,2,3,4),V(5,6)}

(5)下面集合V中和集合E中相连的最小值是4号和6号,所以连接6号城镇;{E(1,2,3,4,6),V(5)}

(6)最后,集合V中和集合E中相连的最小值是6号和5号,所以连接5号城镇;{E(1,2,3,4,6,5),V()}

上面就得出了最后的结果图,所以最小生成树的值为:1+2+9+3+4=19

但是在代码中不易实现,所以就用一个dis数组来记录点到树根的距离,每一次都选取一个到树距离最小的(不是到树根的距离),然后以这个点为中间结点,更新生成树到每一个非树顶点的距离;

代码如下:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

const int N=101;
const int INF=99999999;

int n,m;
int map[N][N];//存储地图;
int book[N];//数组标记这个点是否被加入最小生成树中;
int dis[N];//标记该点到树根的距离;
int countt,sum,minn,u;

void init()//数据初始化;
{
    countt=0;
    sum=0;
    memset(book,0,sizeof book);
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(i==j)
                map[i][j]=0;
            else
                map[i][j]=INF;
        }
    }
    return ;
}

void prim()
{
    book[1]=1;//选取节点1加入生成树,成为树根;
    countt++;//加入生成树中节点的个数;
    while(countt<n)//全部加入生成树中需要进行n-1次的选择;
    {
        minn=INF;
        for(int i=1; i<=n; i++)//每一次都是选取到树根距离最小的点;
        {
            if(book[i]==0&&dis[i]<minn)
            {
                minn=dis[i];
                u=i;
            }
        }
        book[u]=1;
        sum+=dis[u];
        countt++;
        for(int j=1; j<=n; j++)//以上面选取的点为中间点,开始更新生成树的每一个非树顶点的距离;
        {
            if(book[j]==0&&dis[j]>map[u][j])
                dis[j]=map[u][j];
        }
    }
    printf("%d\n",sum);
    return ;
}

int main()
{
    scanf("%d%d",&n,&m);
    init();
    int a,b,c;
    for(int i=0; i<m; i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        map[a][b]=map[b][a]=c;
    }
    for(int i=1; i<=n; i++)
        dis[i]=map[1][i];
    prim();
    return 0;
}

2.Kruskal算法

prim算法,每一次选择的是距离树最近的边加入生成树,Kruskal算法却是选择最短的边,判断要不要加入生成树;

Kruskal算法每一次都是选择一条最短的边,但是在选择的过程中要保证不要形成环路;

                                   

(1),首先,最短的边是(1,2),所以选择边(1,2);

(2),下面就要选择边(1,3);

(3),接着选择边(4,6);

(4),接着选择边(5,6);

(5),下面选择边(2,3),但是会构成环路,所以不选;

(6),下面选择边(4,5),但是也会构成环路,所以不选;

(6),最后只能选择边(3,4);

所以最后的结果是:1+2+3+4+9=19;

Kruskal算法运用了并查集的内容,因为要分辨有没有形成环路;

代码如下:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

const int N=5000;

struct node//存储每一条边
{
    int from;
    int to;
    int w;
}a[N*10];

bool cmp(node aa,node bb)//排序
{
    return aa.w<bb.w;
}

int n,m,k,t;
int f[N];//存储每一个点的父亲节点

int getf(int v)//寻找父亲节点;
{
    if(f[v]==v)
        return f[v];
    else
        return f[v]=getf(f[v]);
}

int Kruskal()
{
    sort(a,a+k,cmp);//按照边的权值由小到大进行排序;
    int ans=0,countt=1;
    for(int i=0;i<k;i++)
    {
        int t1=getf(a[i].from);
        int t2=getf(a[i].to);
        if(t1!=t2)//父亲节点不同,不会构成环路;
        {
            ans+=a[i].w;
            f[t2]=t1;//把父亲节点改成相同;
            countt++;
            if(countt==n)//如果加入n条边就可以结束了;
                break;
        }
    }
    if(countt==n)
        return ans;
    else
        return -1;
}

int main()
{
        scanf("%d%d",&n,&k);
        for(int i=0;i<k;i++)
        {
            scanf("%d%d%d",&a[i].from,&a[i].to,&a[i].w);
        }
        for(int i=0;i<=N;i++)//初始化,自己的父亲节点是自己;
            f[i]=i;
        int kk=Kruskal();
        printf("%d\n",kk);
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值