引言:
在图论中,最小生成树是一个连通图的生成树,它的所有边的权值之和最小。Kruskal和Prim算法是两种常用的生成最小生成树的算法,本文将对它们进行比较和介绍。
技术实现:
一、Kruskal算法:
Kruskal算法是一种基于贪心策略的算法,它通过不断选择边来构建最小生成树。具体步骤如下:
1.定义结构体,定义边集数组。
#include<stdio.h>
#include<stdlib.h>
typedef char VertexType;
typedef int EdgeType;
#define MAXEDGE 15
#define MAXVAX 9
#define INFINITY 65535
typedef struct {
VertexType vex[MAXVAX];//顶点表
EdgeType arc[MAXEDGE][MAXEDGE];//邻接矩阵
int numVertexes, numEdges;//顶点数和边数
}MGraph;
//对边集数组Edge的定义
typedef struct {
int begin;
int end;
int weight;
}Edge;
void MiniSpnTRee_Kruskal(MGraph G);
2.定义一个Find函数,如果数值大于0,则返回。
int Find(int* parent, int f) {
while (parent[f] > 0)
f = parent[f];
return f;
}
3.进行Kruskal算法,定义parent数组判断是否形成回路,利用Find函数计算边的两个顶点数量,如果不等,说明没有环路,入parent数组,再输出边的头尾和权值。
void MiniSpnTRee_Kruskal(MGraph G) {
int i, n, m;
Edge edges[MAXEDGE];//定义边集数组
int parent[MAXVAX];//定义一数组用来判断边与边是否形成环路
for (i = 0; i < G.numVertexes; i++) {
parent[i] = 0;//初始化数组为0
}
for (i = 0; i < G.numEdges; i++) {//循环每一条边
n = Find(parent, edges[i].begin);
m = Find(parent, edges[i].end);
if (n != m) {//假使n与m不等,说明没有生成与现有生成树形成环路
parent[n] = m;//将此边结尾顶点放入下标为起点的parent中,表示顶点已经在生成树中
printf("(%d,%d) %d", edges[i].begin, edges[i].end, edges[i].weight);
}
}
}
二、Prim算法
Prim算法也是一种基于贪心策略的算法,它通过不断选择顶点来构建最小生成树。具体步骤如下:
1. 定义邻接矩阵。
#include<stdio.h>
#include<stdlib.h>
typedef char VertexType;
typedef int EdgeType;
#define MAXEDGE 15
#define MAXVAX 9
#define INFINITY 65535
typedef struct {
VertexType vex[MAXVAX];//顶点表
EdgeType arc[MAXEDGE][MAXEDGE];//邻接矩阵
int numVertexes, numEdges;//顶点数和边数
}MGraph;
void MiniSpanTree_Prim(MGraph G);
2. 进行Prim算法。选择0为起始点,将其加入最小生成树,找到与其相连权值最小的边,将其加入生成树,重复该过程,直到树中有n-1条边。
void MiniSpanTree_Prim(MGraph G) {
int min, i, j, k;
int adjvex[MAXVAX];//保存相关顶点下标
int lowcost[MAXVAX];//保存相关顶点间边的权值
lowcost[0] = 0;//初始化第一个点,即v0加入生成树
adjvex[0] = 0;//初始化第一个顶点下标为0
for (i = 1; i < G.numVertexes; i++) {//循环除下标为0外的全部顶点
lowcost[i] = G.arc[0][i];//将v0顶点与之有边的权值存入数值
adjvex[i] = 0;//初始化都为v0的下标
}
for (i = 1; i < G.numVertexes; i++) {
min = INFINITY;//初始化最小边为无穷
j = 1; k = 0;
while (j < G.numVertexes) {//循环全部顶点
if (lowcost[j] != 0 && lowcost[j] < min) {
min = lowcost[j];//则让当前权值成为最小值
k = j;//将当前最小值的下标存入k
}
j++;
}
printf("(%d,%d)", adjvex[k], k);//打印当前顶点边中权值最小边
lowcost[k] = 0;//将当前顶点权值设置成0,表示已经完成任务
for (j = 1; j < G.numVertexes; j++) {//循环所有顶点
if (lowcost[j] != 0 && G.arc[k][j]) {//若下标为k顶点各边权值小于此前顶点未被加入生成树权值
lowcost[j] = G.arc[k][j];//将较小权值存入lowcost
adjvex[j] = k;//将下标为k的顶点存入adjvex
}
}
}
}
区别:
- 时间复杂度:Kruskal算法的时间复杂度相对较低,适用于边数较多的情况;而Prim算法的时间复杂度较高,适用于顶点数较多的情况。
- 空间复杂度:Kruskal算法需要额外的存储空间来存储并查集,而Prim算法只需要存储最小生成树的顶点集合。
- 实现难度:Kruskal算法相对较容易实现,只需要对边进行排序和并查集操作;而Prim算法需要对顶点进行选择和判断相邻边的权值。
- 边权值:Kruskal算法适用于边权值可以任意的情况;而Prim算法适用于边权值必须为正数的情况。
结论:
Kruskal算法和Prim算法都是生成最小生成树的常用算法,选择哪种算法要根据具体情况来决定。如果边数较多,可以选择Kruskal算法;如果顶点数较多,可以选择Prim算法。同时,还需要考虑边权值的情况,如果边权值可以任意,则可以选择Kruskal算法。