Kruskal算法的C语言实现

Kruskal算法的C语言实现

Kruskal算法生成最小生成树,是一种贪心算法,比Prim还“贪”,每次选择最小权重的边(前提条件:该边+已选取的边不会构成回路(是否构成回路采用并查集判断))当选取边数 == 结点数-1,则必然生成了一棵树且权重和最小

  1. 待处理图采用邻接表和邻接矩阵均可
  2. 最小生成树适合采用邻接表存储(稀疏图)
  3. 每次选择最小权重边,比较low的方法是遍历所有边,这里采用的是最小值堆
  4. 原理挺简单的,实现起来还是需要细心

代码

/*
邻接表存储 - Kruskal最小生成树算法 
@author Fitz
@finish time 2018/11/6
*/
#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 30
/*--------------------数据结构-邻接表存储图-------------------*/

typedef int Vertex;
typedef int WeightType;

/* 边的定义 */
typedef struct ENode *ptrToEdge;
struct ENode {
    Vertex V1;  /* 边起点 */
    Vertex V2;  /* 边终点 */
    WeightType weight;  /* 边权 */
};
typedef ptrToEdge Edge;

/* 邻接点的定义 */
typedef struct AdjVNode *ptrToAdjVNode;
struct AdjVNode {
    Vertex AdjV;    /* 该邻接点 */
    WeightType weight;  /* 该邻接点权值 */
    ptrToAdjVNode next; /* 下一邻接点 */
};

/* 邻接表表头的定义 */
typedef struct VNode {
    ptrToAdjVNode firstEdge;    /* 指向其邻接点的指针 */
}AdjList[MaxVertexNum];

/* 图结点的定义 */
typedef struct LGNode {
    int Nv;     /* 结点数 */
    int Ne;     /* 边数 */
    AdjList G;  /* 邻接表 */
}LGraph;

/* 创建一个Nv个结点无边的图 */
LGraph Create( int Nv )
{
    LGraph graph;
    graph.Nv = Nv;
    graph.Ne = 0;
    /* 初始化邻接表, 起始结点为1*/
    for( Vertex V = 1; V <= graph.Nv; V++ )
    {
        graph.G[V].firstEdge = NULL;
    }
    return graph;   /* 返回该结构体 */
}

/* 向图中插入边 */
void InsertEdge( LGraph* graph, Edge edge )
{
    /* 新建一个邻接点,将边<V1,V2>插入邻接表 */
    ptrToAdjVNode new = (ptrToAdjVNode)malloc(sizeof(struct AdjVNode));
    new->AdjV = edge->V2;
    new->weight = edge->weight;
    new->next = graph->G[edge->V1].firstEdge;
    graph->G[edge->V1].firstEdge = new;

    /* 无向图,将边<V2,V1>插入 */
    new = (ptrToAdjVNode)malloc(sizeof(struct AdjVNode));
    new->AdjV = edge->V1;
    new->weight = edge->weight;
    new->next = graph->G[edge->V2].firstEdge;
    graph->G[edge->V2].firstEdge = new;
}

/* 构建一个完整邻接表存储图 */
LGraph BuildLGraph( )
{
    int Nv;     /* 图结点数 */
    scanf( "%d", &Nv );
    LGraph graph = Create( Nv );
    scanf( "%d", &(graph.Ne));  /* 读入边数 */
    if( graph.Ne )  /* 如果有边 */
    {
        Edge edge = (Edge)malloc(sizeof(struct ENode));
        for( int i = 0; i < graph.Ne; i++ )
        {
            scanf("%d %d %d", &(edge->V1), &(edge->V2), &(edge->weight) );  /* 读入边数据 */
            InsertEdge( &graph, edge );
        }
    }
    return graph;
}

/*------------------------顶点并查集定义------------------------*/
typedef Vertex ElementType; /* 默认元素可以用非负整数表示 */
typedef Vertex SetName;     /* 默认用根节点的下标作为集合名称 */
typedef ElementType SetType[MaxVertexNum];

void InitializeVSet( SetType S, int N )
{ /* 初始化并查集 */
    for( ElementType X = 1 ; X <= N; X++ )
    {
        S[X] = -1;
    }
}

void Union( SetType S, SetName Root1, SetName Root2 )
{ /* 这里默认Root1和Root2是不同集合的根节点 */
    /* 保证小集合并入大集合 */
    if( S[Root2] < S[Root1] )   /* 如果集合2比较大 */
    {
        S[Root2] += S[Root1];   /* 集合1并入集合2 */
        S[Root1] = Root2;
    }
    else{   /* 如果集合1比较大 */
        S[Root1] += S[Root2];
        S[Root2] = Root1;
    }
}

/* 在并查集中寻找元素,返回其所在集合根节点 */
SetName Find( SetType S, ElementType X )
{   /* 默认集合元素全部初始化为-1 */
    SetName ret = -1;
    if( S[X] < 0 )
    {
        ret = X;
    }
    else {
        ret = Find( S, S[X] );
    }
    return ret;
}

int CheckCycle( SetType VSet, Vertex V1, Vertex V2 )
{   /* 检查连接V1和V2的边是否在现有的最小生成树子集中构成回路 */
    int edgeOK = 0;
    Vertex Root1;
    Vertex Root2;

    Root1 = Find( VSet, V1 );   /* 得到V1所属的连通集名称 */
    Root2 = Find( VSet, V2 );   /* 得到V2所属的连通集名称 */

    if( Root1 != Root2 )     /* V1和V2未连通,可以收录这条边,同时将V1,V2并入同一连通集 */
    {
        edgeOK = 1;
        Union( VSet, Root1, Root2 );
    }
    return edgeOK;
}
/*-----------------------并查集定义结束------------------------*/

/*-----------------------边的最小堆定义------------------------*/
void PercDown( Edge ESet, int p, int N )
{ /* 将N个元素的边数组中以ESet[p]为根的子堆调整为关于weight的最小堆 */

    int Child;
    int Parent;
    struct ENode X = ESet[p];   /* 取出根节点存放的值 */
    for( Parent = p; (Parent*2+1) < N; Parent = Child )
    {   /* (Parent*2+1) 保证有子结点,至少左子结点 */
        Child = Parent * 2 + 1;
        if( Child != N-1 && ESet[Child].weight > ESet[Child+1].weight )
        {   /* 有右子结点,且右子节点更小 */
            Child++;
        }       /* Child指向左右子结点的较小者 */
        if( X.weight <= ESet[Child].weight )
        {
            break;      /* 找到合适位置 */
        }
        else {  /* 下滤X */
            ESet[Parent] = ESet[Child];
        }
    }
    ESet[Parent] = X;
}
/*-----------------------边的最小堆定义结束--------------------*/

/* 将图的边存入数组ESet,并且初始化为最小值堆 */
void InitializeESet( LGraph graph, Edge ESet )
{
    int ECount = 0;     /* 边计数器 */
    for( Vertex V = 1; V <= graph.Nv; V++ )
    {
        /* V的所有邻接点 */
        for( ptrToAdjVNode W = graph.G[V].firstEdge; W; W = W->next )
        {
            if( V < W->AdjV )   /* 避免重复录入无向边,只录入V1<V2的边 */
            {
                ESet[ECount].V1 = V;
                ESet[ECount].V2 = W->AdjV;
                ESet[ECount++].weight = W->weight;
            }
        }
    }
    /* 初始化为最小堆 */
    for( ECount = graph.Ne/2; ECount >= 0; ECount-- )
    {
        PercDown( ESet, ECount, graph.Ne );
    }
}

void Swap( Edge a, Edge b )
{
    struct ENode temp = *a;
    *a = *b;
    *b = temp;
}
/* 给定当前堆的大小CurrentSize,将当前最小边位置弹出并调整堆 */
int GetEdge( Edge ESet, int CurrentSize )
{
    /* 将最小边与当前堆的最后一个位置的边交换 */
    Swap( &ESet[0], &ESet[CurrentSize-1] );
    /* 将剩下的边继续调整为最小堆 */
    PercDown( ESet, 0, CurrentSize-1 );

    return CurrentSize-1;   /* 返回最小边所在位置 */
}

/* Kruskal算法 */
int Kruskal( LGraph graph, LGraph *MST )
{   /* 将最小生成树保存未邻接表存储的图MST,返回最小权重和 */
    WeightType TotalWeight;
    int ECount; /* 边计数器 */

    SetType VSet;   /* 顶点数组并查集 */
    InitializeVSet( VSet, graph.Nv );   /* 初始化顶点并查集 */

    Edge ESet = (Edge)malloc(sizeof(struct ENode)*graph.Ne);    /* 边数组(指针) */
    InitializeESet( graph, ESet );  /* 初始化边的最小堆 */

    /* 创建包含所有顶点但没有边的 邻接表图 */
    *MST = Create( graph.Nv );
    TotalWeight = 0;    /* 初始化权重和 */
    ECount = 0;         /* 初始化收录的边数 */

    int NextEdge = graph.Ne;    /* 原始边集的规模 */
    while ( ECount < graph.Nv-1 )   /* 当收录的边不足以构成树时 */
    {
        NextEdge = GetEdge( ESet, NextEdge );   // 妙啊!!!!
        if( NextEdge < 0 )      /* 边集已空 */
        {
            break;
        }
        /* 如果该边的插入不构成回路 */
        if ( CheckCycle(VSet, ESet[NextEdge].V1, ESet[NextEdge].V2) )
        {
            /* 将该边插入MST */
            InsertEdge( MST, ESet+NextEdge );
            TotalWeight += ESet[NextEdge].weight;
            ECount++;   /* 生成树中的边数加1 */
        }
    }
    if ( ECount < graph.Nv - 1 )
    {
        TotalWeight = -1;
    }
    return TotalWeight;
}

/*------------终于到main了!!--------------------*/
int main(int argc, char const *argv[])
{
    /* 构建图 */
    LGraph graph = BuildLGraph( );
    LGraph *MST = (LGraph*)malloc(sizeof(LGraph)); /* 最小生成树指针 */
    WeightType  TotalWeight = Kruskal( graph, MST );
    printf( "%d", TotalWeight );

    return 0;
}
/*
测试数据
7 12
1 2 2
1 4 1
1 3 4
2 5 10
2 4 3
5 4 7
5 7 6
7 4 4
7 6 1
6 4 8
6 3 5
3 4 2
最小生成树的权值和:16
 */
  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值