Kruskal算法的C语言实现
Kruskal算法生成最小生成树,是一种贪心算法,比Prim还“贪”,每次选择最小权重的边(前提条件:该边+已选取的边不会构成回路(是否构成回路采用并查集判断))当选取边数 == 结点数-1,则必然生成了一棵树且权重和最小
- 待处理图采用邻接表和邻接矩阵均可
- 最小生成树适合采用邻接表存储(稀疏图)
- 每次选择最小权重边,比较low的方法是遍历所有边,这里采用的是最小值堆
- 原理挺简单的,实现起来还是需要细心
代码
/*
邻接表存储 - 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
*/