一、克鲁斯卡尔算法
1、无论是普里姆算法(Prim)还是克鲁斯卡尔算法(Kruskal),考虑问题的出发点都是:为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能的小。但是普里姆算法是以某顶点为起点,逐步找各个顶点上最小权值的边来构建最小生成树的。换一种思考方式,从边出发,因为权值是在边上,直接去找最小权值的边来构建生成树的想法就是克鲁斯卡尔算法的精髓。
2、如下图所示,采用边集数组存储结构来存储无向网图,边集数组的元素是按照权值从小到大排序的。
那么采用克鲁斯卡尔算法生成最小生成树的过程如下:
- 首先根据边集数组按权值递增的顺序将连接了两个顶点的边(0, 2)、(3, 5)、(1, 4)、(2, 5)依次变成红色:
- 接着,到了边(0, 3),但是由于边(0, 3)的两个顶点在同一棵树上(0, 2, 5, 3),如果将该边变成红色,那么将形成一个回路,肯定是不满足生成树条件的,所以舍去;接着轮到边(1, 2)和(2, 4),由于它们的权值相同,所以任选其中一条变成红色:
- 此时,最小生成树其实就已经构成了,剩下的边(0, 1)、(4, 5)、(2, 3)随便哪一条变成红色,都将会形成回路,更不可能形成生成树,所以此时图中的红色边与相关顶点组成的树就是最小生成树。
3、完整实现代码如下:
/************************************************************************/
/** 最小生成树算法之克鲁斯卡尔算法 **/
/************************************************************************/
#include <stdio.h>
#define MAX_GRAPH_VERTEX_SIZE 100 // 最大顶点数
#define MAX_GRAPH_EDGE_SIZE 100 // 边集数组最大元素个数
typedef char VertexType; // 顶点类型
typedef int WeightType; // 权值类型
// 定义边集数组元素类型
typedef struct Edge
{
int begin;
int end;
WeightType weight;
}Edge;
// 定义图结构
typedef struct Graph
{
VertexType vexs[MAX_GRAPH_VERTEX_SIZE]; // 顶点表
Edge edges[MAX_GRAPH_EDGE_SIZE]; // 边集数组
int numVertexes, numEdges; // 当前顶点个数和边数
}Graph;
/**
* 创建图
* @param graph:指向图结构的指针
*/
void CreateGraph(Graph *graph)
{
int i, j, k, w;
printf("请输入无向网图的顶点个数和边数,分别以空格分隔:");
scanf("%d %d", &(graph->numVertexes), &(graph->numEdges));
fflush(stdin);
for(i = 0; i < graph->numVertexes; i++)
{
printf("请输入第%d个顶点的顶点信息:", i + 1);
scanf("%c", &(graph->vexs[i]));
fflush(stdin);
}
printf("请按照权值从小到大的顺序输入边的起点和终点下标以及权值:\n");
for(k = 0; k < graph->numEdges; k++)
{
printf("请输入边(vi, vj)所在顶点在顶点表中的下标i和vj,以及该边上的权值w,分别以空格分隔:");
scanf("%d %d %d", &i, &j, &w);
fflush(stdin);
graph->edges[k].begin = i;
graph->edges[k].end = j;
graph->edges[k].weight = w;
}
}
int Find(int *parent, int f)
{
while(parent[f] > 0)
{
f = parent[f];
}
return f;
}
/**
* 克鲁斯卡尔算法生成最小生成树
*/
void Kruskal(Graph graph)
{
int i, n, m;
int parent[MAX_GRAPH_VERTEX_SIZE]; // 定义parent数组用来判断边与边是否形成环路
for(i = 0; i < graph.numVertexes; i++)
{
parent[i] = 0;
}
for(i = 0; i < graph.numEdges; i++)
{
n = Find(parent, graph.edges[i].begin);
m = Find(parent, graph.edges[i].end);
// 如果n=m,则形成环路
if(n != m)
{
parent[n] = m; // 将此边的结尾顶点放入下标为起点的parent数组中,表示此顶点已经在生成树集合中
printf("(%d %d) %d ", graph.edges[i].begin, graph.edges[i].end, graph.edges[i].weight);
}
}
}
int main()
{
Graph graph;
CreateGraph(&graph);
Kruskal(graph);
return 0;
}
求上面那个图的最小生成树,运行结果如下图: