63-Kruskal算法

  Kruskal算法(也称为克鲁斯卡尔算法)也是一种用来寻找最小生成树的算法 。与Prim算法不同的是,Prim算法是逐个加入权值最小边的顶点方法,而Kruskal算法则是对每条边按权值的递增次序选择合适的边。

问题:
  G=(V,E)是一个具有n个顶点的带权连通无向图

  T = (U,TE)是图G的最小生成树,其中U是T的顶点集合,TE是T的边集合

这里写图片描述
图1-图G

由图G构造最小生成树步骤:
  1. 置U的初值等于V(也就是说,集合U中包含了图G中的全部顶点),TE的初值为空集(即图T中每一个顶点都构成一个分量) 。

  2. 将图G中的边按权值从小到大的顺序依次选取:若选取的边未使生成树T形成回路,则加入TE;否则舍弃,直到TE中包含(n-1)条边为止。


1. Kruskal算法构造生成树过程

由上面的图G来选择权值最小的边构造最小生成树,过程如下:

这里写图片描述
图2

  首先,我们通过查找图G发现,权值为1的边是最小的,于是我们就把(0,2)这条边加入进来。

扫描二维码关注公众号,回复: 2811997 查看本文章



这里写图片描述
图3

  再次查找图G发现权值为2的这条边是最小的,于是我们再把(3,5)这条边加入进来。



这里写图片描述
图4

  同理,把权值最小的(1,4)这条边加入进来,把(2,5)这条边加入进来。



这里写图片描述
图5

  当再次从图G中搜索时发现,权值最小,且权值为5的边有三条:(0,3),(2,3),(1,2),但是(0,3)和(2,3)这两条边会出现回路问题,这是在构造最小生成树不允许出现的,因此(0,3)和(2,3)这两条边放弃加入,只剩下(1,2)这条边可以选择了,于是把(1,2)这条边加入进来,组成最小生成树。

  所以在加入一条边的时候,还必须判断这条边是否会出现回路,如果出现回路那么就要放弃加入这条边,而这个判断的过程需要借助一个vset数组来完成 。



这里写图片描述
图6

  我们假设图G是采用邻接矩阵方式存储的,再通过定义一个数组E来存储图G中的每一条边,按权值升序排列的方式存储边。

  比如(0,2)这条边的权值是1,那么在数组E中存储就是n = 0,v = 2,w = 1。也就是说n代表(0,2)这条边的起始点,v是(0,2)这条边的终点,而w是(0,2)边的权值。其他的边和权值都是以这种方式来存储的。
当我们把图G中的边和权值全部都存储到数组E中,然后再按照权值升序进行排列方式对数组E中所有的元素进行排序就可以了

  对应的数组E的存储结构定义如下:

//按权值升序排列的边
typedef struct
{
    int u;  //边的起始点
    int v;  //边的终点
    int w;  //边的权值
} Edge;     //数组E

  上面我们说到的这个数组E非常重要,需要存储图的边,并按次序排列,特别是当我们在构造生成树的时候,就需要从数组E中来选择合适的边;而数组vset同样也很重要,因为我们在选择加入一条边的时候,还需要判断是否会出现回路,这些是Kruskal算法需要解决的问题,那么下面我们来看一下Kruskal算法的实现。


2. Kruskal算法示例

这里写图片描述
图7

  通过vset数组对每一个顶点进行编号,vset数组中元素的值就代表子图连通分量,用于标识子图是否属于同一连通分量。而在刚开始时,所有的顶点是没有边的,也就是说所有顶点都是属于不同的连通分量,所以刚开始vset数组中元素的值也是不同的,比如顶点0的连通分量是0,顶点1的连通分量是1。

  因此我们从数组E中搜索权值最小的边,于是我们从数组E中搜索到(0,2)这条边,并把这条边加入到集合TE中组成生成树。把顶点0和顶点2连接起来后,那么顶点0和顶点2就是属于一个子图连通分量了,那么对应的在vset数组中会做一些相应的修改:在vset数组中把vset[2] = 2修改为0,表示顶点0和顶点2都是属于同一子图的连通分量0。

这里写图片描述
图8

  同理,我们从数组E中搜索到(3,5)这条权值最小的边,把(3,5)这条边加入到集合TE中。并在vset数组中把vset[5] = 5修改为vset[5] = 3,表示顶点3和顶点5属于连通分量3。

这里写图片描述
图9

   同理,把(1,4)这条边加入进来。在vset数组中修改vset[4]=4修改为vset[4]=1。

这里写图片描述
图10

   同理,我们从数组E中搜索到(2,5)这条权值最小的边,把(2,5)这条边加入到集合TE中。此时我们发现,把连通分量0和连通分量3连接起来,合并成一个大的连通分量0了。由于(2,5)这条边中顶点2为起始点,顶点5为终点,所以把终点所在的连通分量合并为起点所在的连通分量,因此在vset数组中把vset[5] 和vset[3]的值修改为0,表示顶点3和顶点5属于连通分量0。

这里写图片描述
图11

   同理,当把(1,2)这条边加入进来后,连通分量1和连通分量0合并成一个大的连通分量1了。由于(1,2)这条边中顶点1是起始点,而顶点2是重点,所以把终点所在的连通分量合并为起始点所在的连通分量。因此在vset数组中把vset[0] ,vset[2],vset[3],vset[5]的值修改为1,表示顶点0,顶点2,顶点3,顶点5属于连通分量0。最后所有的顶点都属于一个连通分量。

   此时集合TE = {(0,2),(3,5),(1,4),(2,5),(1,2)},最小生成树就构造完成了。


3. Kruskal算法实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXV 6
#define MAXSIZE 100
#define INF 99

//图的定义:邻接矩阵
typedef struct MGRAPH{
    int n;                  //顶点数
    int e;                  //边数
    int edges[MAXV][MAXV];  //邻接矩阵
} MGraph;


//按权值升序排列的边
typedef struct  EDGE
{
    int u;
    int v;
    int w;
} Edge;


//插入排序
void InsertSort(Edge E[],int n) 
{
    int i,j;
    Edge temp;
    for (i=1; i<n; i++)
    {
        temp=E[i];
        j=i-1;             
        while (j>=0 && temp.w<E[j].w)
        {
            E[j+1]=E[j];    
            j--;
        }
        E[j+1]=temp;
    }
}


void Kruskal(MGraph g)
{
    int i,j,u1,v1,sn1,sn2,k;
    int vset[MAXV];
    Edge E[MAXSIZE];

    //通过邻接矩阵来构造E数组并排序
    k=0;
    for (i=0; i<g.n; i++)
    {
        for (j=0; j<g.n; j++)
        {
            //权值非0,非无穷大
            if (g.edges[i][j]!=0 && g.edges[i][j]!=INF)
            {
                E[k].u=i;
                E[k].v=j;
                E[k].w=g.edges[i][j];
                k++;
            }
        }

    }

    //排序
    InsertSort(E,g.e);

    //开始初始化辅助数组vset
    for (i=0; i<g.n; i++)
    {   
        vset[i]=i;
    }


    //k从1开始,选出n-1条边
    k=1; j=0;
    while (k<g.n)
    {
        //起始点
        u1=E[j].u;
        //终点
        v1=E[j].v;
        //起始点的连通分量
        sn1=vset[u1];
        //终点的连通分量
        sn2=vset[v1];
        //是否为同一连通分量
        if (sn1 != sn2)
        {
            //不是同一连通分量就输出边的信息
            printf(" (%d,%d),权值:%d\n",u1,v1,E[j].w);
            //k用于记录搜索的边数
            k++;
            //然后修改vset数组,把终点的连通分量合并为起始点的连通分量
            for (i = 0; i < g.n; i++)
            {
                if (vset[i] == sn2) 
                {
                    vset[i] = sn1;
                }
            }

        }
        //j用于记录搜索的每个顶点
        j++;
    }
}


int main(void)
{
    //用99数值代表无穷大
    int A[MAXV][MAXV]=
    {
        {0,6,1,5,INF,INF},
        {6,0,5,INF,3,INF},
        {1,5,0,5,6,4},
        {5,INF,5,0,INF,2},
        {INF,3,6,INF,0,6},
        {INF,INF,4,2,6,0}
    };

    int i;
    int j;
    //定义邻接矩阵存储结构
    //另外,程序在优化的时候,是可以采用对称矩阵方式存储的
    MGraph g;
    //边数
    g.e = 19;
    //顶点数
    g.n = 6;
    for(i = 0; i < g.n; i++)
    {
        for(j = 0; j < g.n; j++)
        {
            g.edges[i][j] = A[i][j];
        }
    }

    printf("\n");
    printf("最小生成树:\n");
    Kruskal(g);
    printf("\n");
    return 0;
}

测试结果:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_35733751/article/details/81228346