最小生成树-贪心(Kruskal算法(克鲁斯卡尔算法))

问题描述

 设G = (V, E)是一个无向连通带权图,即一个网络。E的每条边(v, w)的权为c[v][w]。

 如果G的一个子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。

 生成树的各边权的总和称为该生成树的耗费

 在G的所有生成树中,耗费最小的生成树称为G的最小生成树MST(minimum spanning tree) 。


算法思想

  • 在保证无回路的前提下依次选出权重较小的n – 1条边
  • 贪心策略:如果(i, j)是E中尚未被选中的边中权重最小的,并且(i, j)不会与已经选择的边构成回路,于是就选择 (i, j)。

如何知道(i, j)不会造成回路?

  • 为此初始时将图的n个顶点看成n个孤立连通分支,将所有的边按权由小到大排序, 并依边权递增顺序检查每一条边:
  • 若边(i, j) 的两个端点i和j属于同一个连通分支,则选择(i, j) 会造成回路,反之则不会造成回路。
                在这里插入图片描述

Kruskal算法的数据结构

  • 数组e[][]表示图的边,e[i][u]、e[i][v]和e[i][w]分别表示边i的两个端点及其权重。
  • 函数Sort(e, w)将数组e按权重w从小到大排序。
  • 一个连通分支中的顶点表示为一个集合。
  • 函数Initialize(n)将每个顶点初始化为一个集合。
  • 函数Find(u)给出顶点u所在的集合。
  • 函数Union(a, b)给出集合a和集合b的并集。
  • 重载算符!=判断集合的不相等。

在这里插入图片描述


核心代码

Kruskal(int n, **e)  {
  Sort(e, w); //将边按权重从小到大排序
  initialize(n); //初始时每个顶点为一个集合
  k = 1; //k累计已选边的数目,
  j = 1;  //j为所选的边在e中的序号
  while (k < n) //选择n – 1条边
    {a = Find(e[j][u]); b = Find(e[j][v]); 
   //找出第j条边两个端点所在的集合
     if (a != b) {t[k++] = j; Union(a, b)}
   //若不同,第j条边放入树中并合并这两个集合
     j++ } //继续考察下一条边
}

例子

给定一个连通带权图如下:
          在这里插入图片描述

 首先,在初始状态下,对各顶点赋予不同的标记(用颜色区别),如下图所示:
              在这里插入图片描述

 对所有边按照权值的大小进行排序,按照从小到大的顺序进行判断,首先是(1,3),由于顶点 1 和顶点 3 标记不同,所以可以构成生成树的一部分,遍历所有顶点,将与顶点 3 标记相同的全部更改为顶点 1 的标记,如(2)所示:
              在这里插入图片描述
 其次是(4,6)边,两顶点标记不同,所以可以构成生成树的一部分,更新所有顶点的标记为:
              在这里插入图片描述
 其次是(2,5)边,两顶点标记不同,可以构成生成树的一部分,更新所有顶点的标记为:
              在这里插入图片描述
 然后最小的是(3,6)边,两者标记不同,可以连接,遍历所有顶点,将与顶点 6 标记相同的所有顶点的标记更改为顶点 1 的标记:
              在这里插入图片描述
 继续选择权值最小的边,此时会发现,权值为 5 的边有 3 个,其中(1,4)和(3,4)各自两顶点的标记一样,如果连接会产生回路,所以舍去,而(2,3)标记不一样,可以选择,将所有与顶点 2 标记相同的顶点的标记全部改为同顶点 3 相同的标记:
              在这里插入图片描述

当选取的边的数量相比与顶点的数量小 1 时,说明最小生成树已经生成。

完整算法

#include<iostream>

#include <stdio.h>
#include <stdlib.h>

using namespace std;

typedef struct  Edge				//定义边集数组元素,v1,v2存顶点,weight存权重。
{
    int v1;
    int v2;
    int weight;
}Edge;

typedef struct ALGraph		//定义图的结构,peak存顶点的数量,edge存边的数量
{											//指针p作为边集数组,指针m为作为顶点数组
    int peak;
    int edge;
    Edge *p;
    int *m;
}ALGraph;


//创建图
void CreatALGraph(ALGraph *G)
{
    int i,j;
    printf("输入图的顶点数量和边的数量:");
    scanf("%d %d",&G->peak,&G->edge);
    G->p=(Edge *)malloc(sizeof(Edge)*(G->edge+1));
    G->m=(int *)malloc(sizeof(int)*G->peak);
    for(i=0;i<G->peak;i++)
    {
          printf("请输入输入顶点:");
          scanf("%d",&G->m[i]);
    }
    for(i=0;i<G->edge;i++)
    {
        printf("请输入(vi-vj)和权重:");
        scanf("%d %d %d",&G->p[i].v1,&G->p[i].v2,&G->p[i].weight);
    }
    for(i=0 ;i<G->edge;i++)				//冒泡排序法,权重从小到大存在边集数组中
    {
        for(j=G->edge-1;j>i;j--)
        {
            if(G->p[i].weight>G->p[j].weight)
            {
                G->p[G->edge]=G->p[i];
                G->p[i]=G->p[j];
                G->p[j]=G->p[G->edge];
            }
        }
    }
}


//通过parent[]找到可连接的边
int Find(int *parent,int g)
{
    while(parent[g]!=0)
    {
        g=parent[g];
    }
    return g;
}

//判断生成树是否完成,完成的标志是生成树的边等于顶点的数量减1
int Finish(ALGraph *G,int *parent)
{
    int i,n=0;

    for(i=0;i<G->peak;i++)
    {
        if(parent[i])
        {
            n++;
        }
    }
    if(n==G->peak-1)
    {
        return 1;
    }
    return 0;
}

//找到顶点的下标

int FindPeak(ALGraph *G,int g){
    int i;
    for(i=0;i<G->peak;i++)
    {
        if(G->m[i]==g)
            return i;
    }
    return -1;
}
void MinTree_Kruskal(ALGraph *G)
{
    int i,a,b;

    int parent[G->peak];

    for(i=0;i<G->peak;i++)		//初始化parent[]
    {
        parent[i]=0;
    }
    for(i=0;i<G->edge;i++)
    {
        a=Find(parent,FindPeak(G,G->p[i].v1));
        b=Find(parent,FindPeak(G,G->p[i].v2));

        if(a!=b)				//如果a==b则表是a和b在同一颗生成树上,如果a和b连接则为生成环,不符合生成树
        {
            parent[a]=b;
            printf("%d->%d   %d\n",G->p[i].v1,G->p[i].v2,G->p[i].weight);
        }
        if(Finish(G,parent))		//完成后返回
        {
            return;
        }
    }
}

int  main()
{
    ALGraph *G=(ALGraph *)malloc(sizeof(ALGraph));
    CreatALGraph(G);
    MinTree_Kruskal(G);
    return 0;
}

测试样例

测试样例是上面例子中的数据,但是注意这里是从0开始的

输入
输入图的顶点数量和边的数量:6 10
请输入输入顶点:0
请输入输入顶点:1
请输入输入顶点:2
请输入输入顶点:3
请输入输入顶点:4
请输入输入顶点:5
请输入(vi-vj)和权重:0 1 6
请输入(vi-vj)和权重:0 2 1
请输入(vi-vj)和权重:0 3 5
请输入(vi-vj)和权重:1 2 5
请输入(vi-vj)和权重:1 4 3
请输入(vi-vj)和权重:2 3 5
请输入(vi-vj)和权重:2 4 6
请输入(vi-vj)和权重:2 5 4
请输入(vi-vj)和权重:3 5 2
请输入(vi-vj)和权重:4 5 6

输出
0->2 1
3->5 2
1->4 3
2->5 4
1->2 5

在这里插入图片描述

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值