C语言实现邻接矩阵转换成边集数组,克鲁斯卡尔(Kruskal)算法,并对其优化

将边从小到大顺序排列,从最小边的开始,逐一将边加入最小生成树中,但有一个问题是要判断加入边后,是否会产生回路。解决方法:通过访问边的起点和终点是否在同一个集合来判断该边加入生成树后是否会形成环路。
克鲁斯卡尔算法实际上是并查集的一个应用。
关于并查集的讲解,我十分推荐数据结构4——并查集(入门)这篇博客。

将邻接矩阵转换成边集数组:

思路:由于是无向图,,邻接矩阵是一个对称矩阵,因此只需要遍历邻接矩阵的上半个三角形。

边集数组的结构定义:

typedef struct
{
    int begin ;
    int end ;
    int weigth ;
}EdgeAdjlist[ MAXSIZE ] , EdgeNode ;

邻接矩阵转换成边集数组:

void TransEdge( EdgeAdjlist edge , MGraph G )
{
    int i , j  , k = 0  ;
    EdgeNode t ;
    for( i = 0 ; i < G.VertexNum ; i++ )
        for(  j = i + 1 ; j < G.VertexNum ; j++ )
            if( G.arcs[ i ][ j ] > 0 && G.arcs[ i ][ j ] < MAXINT )
            {
                edge[ k ].begin = i ;
                edge[ k ].end = j ;
                edge[ k ].weigth = G.arcs[ i ][ j ] ;
                k++ ;
            }

    for( i = 0 ; i < G.EdgeNum - 1 ; i++ )
        for( j = 0 ; j < G.EdgeNum - i - 1 ; j++ )
            if( edge[ j ].weigth > edge[ j + 1 ].weigth )
            {
                t = edge[ j ] ;
                edge[ j ] = edge[ j+ 1 ] ;
                edge[ j+ 1 ] = t ;
            }
}

find函数:

创建一个parent数组来存储顶点的父节点,通过find函数来找到该节点所在集合的根节点,记住,parent[ i ] = 0 表示的是:i 结点本身就是结点所在集合的根节点,也就是说这个结点自己构成一个集合

int find( int x )
{
    while( parent[ x ] > 0 )
        x = parent[ x ] ;
    return x ;
}

//该函数的优化:
int find( int x )
{
    if( parent[ x ] > 0 )
        x = find( parent[ x ] ) ;
    return x ;
}

优化采用了压缩路径的方法,直接让子孙顶点指向了集合的根节点,这样就大大减少了以后寻找集合的根节点的时间。

克鲁斯卡尔算法:

void MiniSpanTree_Kruskal( MGraph G )
{
    int i ,  n , m ;
    EdgeAdjlist edge ;

    TransEdge(  edge ,  G ) ;

    for( i = 0 ; i < G.VertexNum ; i++ )
        parent[ i ] = 0 ;

    for( i = 0 ; i < G.EdgeNum ; i++ )
    {
        n = find( edge[ i ].begin ) ;
        m = find( edge[ i ].end ) ;

        if( n != m )
        {
            parent[ n ] = m ;
            printf( "%c ===== %c , %d\n" , G.vexs[ edge[ i ].begin ] , G.vexs[ edge[ i ].end ] , edge[ i ].weigth ) ;
        }
    }
}

有这样一个无向图(图是从《大话数据结构》里盗的):
在这里插入图片描述
其边集数组为:
在这里插入图片描述

我们来看看代码是如何让运行的:
还要再强调一遍的是:parent[ i ] = 0 表示的是:i 结点本身就是结点所在集合的根节点,也就是说这个结点自己构成一个集合

当i=0时:第一条边加入了最小生成树中,形成了这样一棵树:
在这里插入图片描述
同理i=1,i=2,又生成了两颗独立的树:
在这里插入图片描述
这时候,parent数组的值为:[ 1,0,8,0,7,0,0,0,0 ] ,说明了v0的父结点是v1,v2的v8,v4的是v7.

**当i=3时:要加入的边为v0-----v5,因为此时v0父节点是v1,v5父节点是自己,两个结点不在同一个集合内,因此可以加入这条边。最后v5成为了这个集合的根结点,**如图:
在这里插入图片描述
此时,parent的值为:[ 1,5,8,0,7,0,0,0,0 ]

i=4,5,6都是同理,最后生成了两颗独立的树:
在这里插入图片描述
此时的parent数组的值:[ 1,5,8,7,7,8,0,0,6 ]

但是当i=7时,要加入的边是v5------v6,通过find函数可知,v5所在集合的根节点是v6,因此v5和v6是在同一个集合里面,如果加入了这条边,那么就会形成回路!

同理,i=8时,要加入的边是v1-----v2,两个结点所在集合的根节点均为v6,这条边也不能加入。

当i=9时,边v6-----v7的两个结点所在集合的根节不是同一个结点,因此,这条边可以加入最小生成树中:
在这里插入图片描述
当 i 继续增大,后面的边均会生成环路。最终parent数组的值为:[ 1,5,8,7,7,8,7,0,6 ]
需要注意的是:这里的树并不是Kruskal生成的真正的树,就比如:上图的v8和v5再真正的生成子树中并没有直接关系。我们用parent数组标记生成子树的根节点,只是为了找到分别独立的集合中的一个代表,结点通过找到这个代表就能知道自己属于哪一个集合了,当然了,任何一个结点都能作为集合的代表。

最后生成的最小生成树:
在这里插入图片描述

再附上优化后的源代码:

#include<stdio.h>
#define MAXSIZE 100
#define MAXINT 65535

typedef struct
{
    char vexs[ MAXSIZE ] ;
    int arcs[ MAXSIZE ][ MAXSIZE ] ;
    int VertexNum , EdgeNum ;
}MGraph ;

typedef struct EdgeNode
{
    int begin ;
    int end ;
    int weigth ;
}EdgeAdjlist[ MAXSIZE ] , EdgeNode ;

void CreateMGraph( MGraph *G ) ;
int Locate( MGraph G , char ch ) ;
void TransEdge( EdgeAdjlist edge , MGraph G ) ;
void MiniSpanTree_Kruskal( MGraph G ) ;

int find( int x ) ;
int parent[ MAXSIZE ] ;

int main( void )
{
    MGraph G ;
    CreateMGraph( &G ) ;
    MiniSpanTree_Kruskal( G ) ;


    return 0 ;
}

int Locate( MGraph G , char ch )//找到结点在顶点数组中的下标
{
    int i ;
    for( i = 0 ; i < G.EdgeNum ; i++ )
        if( ch == G.vexs[ i ] )
            break ;
    return i ;
}

void CreateMGraph( MGraph *G )//创建邻接矩阵
{
    int i , weight , pos1 , pos2 ;
    char x , y ;
    printf( "请输入图中结点的个数:" ) ;
    scanf( "%d" , &G->VertexNum ) ;
    getchar() ;

    for( i = 0 ; i < G->VertexNum ; i++ )
    {
        printf( "请输入第%d个结点的信息:" , i + 1 ) ;
        scanf( "%c" , &G->vexs[ i ] ) ;
        getchar() ;
    }

    printf( "请输入图的边的条数:" ) ;
    scanf( "%d" , &G->EdgeNum ) ;
    getchar() ;

    for( i = 0 ; i< G->EdgeNum ; i++ )
    {
        printf( "请输入第%d条边的两个结点信息,和边的权值:" , i+ 1 ) ;
        scanf( "%c %c %d" , &x , &y , &weight ) ;
        getchar() ;

        pos1 = Locate( *G , x ) ;
        pos2 = Locate( *G , y ) ;

        G->arcs[ pos1 ][ pos2 ] = weight ;
        G->arcs[ pos2 ][ pos1 ] = weight ;
    }
}

void TransEdge( EdgeAdjlist edge , MGraph G )
{
    int i , j  , k = 0  ;
    EdgeNode t ;
    for( i = 0 ; i < G.VertexNum ; i++ )
        for(  j = i + 1 ; j < G.VertexNum ; j++ )
            if( G.arcs[ i ][ j ] > 0 && G.arcs[ i ][ j ] < MAXINT )
            {
                edge[ k ].begin = i ;
                edge[ k ].end = j ;
                edge[ k ].weigth = G.arcs[ i ][ j ] ;
                k++ ;
            }

    for( i = 0 ; i < G.EdgeNum - 1 ; i++ )
        for( j = 0 ; j < G.EdgeNum - i - 1 ; j++ )
            if( edge[ j ].weigth > edge[ j + 1 ].weigth )
            {
                t = edge[ j ] ;
                edge[ j ] = edge[ j+ 1 ] ;
                edge[ j+ 1 ] = t ;
            }
}

int find( int x )//找结点所在集合的根节点
{
    if( parent[ x ] > 0 )
        x = find( parent[ x ] ) ;
    return x ;
}

void MiniSpanTree_Kruskal( MGraph G )
{
    int i ,  n , m ;
    EdgeAdjlist edge ;

    TransEdge(  edge ,  G ) ;

    for( i = 0 ; i < G.VertexNum ; i++ )
        parent[ i ] = 0 ;

    for( i = 0 ; i < G.EdgeNum ; i++ )
    {
        n = find( edge[ i ].begin ) ;
        m = find( edge[ i ].end ) ;

        if( n != m )
        {
            parent[ n ] = m ;
            printf( "%c ===== %c , %d\n" , G.vexs[ edge[ i ].begin ] , G.vexs[ edge[ i ].end ] , edge[ i ].weigth ) ;
        }
    }
}
  • 6
    点赞
  • 29
    收藏
  • 打赏
    打赏
  • 2
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:创作都市 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

h3xz

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值