PS:本代码实现受此文启发
Kruskal算法代码实现需要两步:
①弧结点按照权重从小到大排序(注意交换的时候是交换结构体)
②判断这个弧能不能成环,如果不能成环,就加入到其中
对于第二点的实现分为了两步,首先有一个Parent整型数组,长度为弧的数目,初始化为0,在程序运行过程中,如果Parent[i]为0,说明这是根或者是一个只有根节点的树,如果不为0,说明有父亲,但不一定是根,所以需要寻找父亲的父亲,直到找到Parent[n] = 0,那么n才是最终的根节点,这就是Find_Root函数的作用。
那么,为什么可以这样判断呢?这就是实现的第二步,每当我们选取一条弧的时候,我们都会把Parent[head] 更新为tail(之前确保了head<tail),这样就让大的值做了root。如果成环,就必定有两个树的root相同,所以我们选择root不同的树加入。
测试图:
网的内容从文本文档Krusral.txt中读取 文档内容如下
运行情况:
代码如下:
/*
实现内容:
①无向网的数组表示法储存实现
②克鲁斯卡尔算法
数组表示法:分两部分,一个顶点数组,一个邻接矩阵
两结点间没有连接 用MAX_INT表实
VS2019编译通过 2020.7.31
*/
#define _CRT_SECURE_NO_WARNINGS
#define FILE_BUFFER_LENGTH 30000
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <memory.h>
#define ALGraphType char
#define Max_Vertices 100
typedef enum GRAKIND { DG, UG, DN, UN } GraKind;
//图的顶点定义
typedef struct VNODE {
ALGraphType Date;
}VNode;
typedef struct ALGRAPH {
VNode* Vertices;
int Vexnum, Arcnum; //顶点数目、边的数目
int** Arc_Matrix;
GraKind Kind;
}ALGraph;
//弧结点 PS:数大的是End
typedef struct _EDGE {
int Begin;
int End;
int Weight;
}Edge;
//根据顶点信息,找到顶点在顺序存储的位置(默认顶点按照字母顺序编号)
int Locate(ALGraphType ch) {
return toupper(ch) - 'A';
}
//创建图
void Creat_ALGraph(ALGraph* G) {
if (NULL == G)
return;
//从文件中读取数据
FILE* fp = fopen("C:\\Users\\86132\\Desktop\\第十周周末程序设计\\克鲁斯卡尔算法(邻接矩阵表示法)\\Kruskal.txt", "r");
fscanf(fp, "%d", &G->Vexnum);
G->Vertices = (VNode*)malloc(sizeof(VNode) * G->Vexnum);
fscanf(fp, "%d", &G->Arcnum);
G->Arc_Matrix = (int**)malloc(sizeof(int*) * G->Vexnum);
//初始化邻接矩阵
for (int i = 0; i < G->Vexnum; i++) {
G->Arc_Matrix[i] = (int*)malloc(sizeof(int) * G->Vexnum);
//初始赋值MAX_INT
for (int j = 0; j < G->Vexnum; j++)
G->Arc_Matrix[i][j] = INT_MAX;
}
//录入顶点信息
for (int i = 0; i < G->Vexnum; i++) {
//空格作用为不读回车
fscanf(fp, " %c", &G->Vertices[i].Date);
}
//录入每个边的信息(默认顶点按照ABCD编号)
for (int i = 0; i < G->Arcnum; i++) {
char ch1, ch2;
int n;
空格作用为不读回车
fscanf(fp, " %c %c%d", &ch1, &ch2, &n);
//更新邻接矩阵
ch1 = Locate(ch1);
ch2 = Locate(ch2);
G->Arc_Matrix[ch1][ch2] = G->Arc_Matrix[ch2][ch1] = n;
}
fclose(fp);
}
//冒泡排序辅助函数
void Exchange(Edge* a, Edge* b) {
Edge c = *a;
*a = *b;
*b = c;
}
//寻找根节点 辅助判断此边能不能加入
int Find_Root(int* Parent, int i) {
//证明上一级有结点
while (0 < Parent[i])
i = Parent[i];
return i;
}
//克鲁斯卡尔算法 输出图G最小生成树的边
void Mini_Span_Tree_Kruskal(ALGraph G) {
//从数组表示法得到边的信息
Edge* Edges = (Edge*)malloc(sizeof(Edge) * G.Arcnum);
//更新数组Edges的内容
int tmp = 0;
for (int i = 0; i < G.Vexnum; i++) {
//由于是无向网 所以只需要循环一半矩阵就可以
for (int j = i + 1; j < G.Vexnum; j++) {
if (INT_MAX != G.Arc_Matrix[i][j]) {
Edges[tmp].Begin = i;
Edges[tmp].End = j;
Edges[tmp++].Weight = G.Arc_Matrix[i][j];
}
}
}
//权重需要从小到大排序
//改进版冒泡排序
for (int i = 0; i < G.Arcnum; i++) {
//flag记录是否交换 LastPos记录最后一次排序位置
int flag = 0, LastPos = G.Arcnum, k = G.Arcnum - 1;
for (int j = 0; j < k; j++) {
if (Edges[j].Weight > Edges[j + 1].Weight) {
Exchange(&Edges[j], &Edges[j + 1]);
flag = 1;
LastPos = j + 1;
}
}
if (0 == flag)
break;
k = LastPos;
}
//通过Parent数组来寻找此子树的根结点 0表示自己成根 无儿子
int* Parent = (int*)malloc(sizeof(int) * G.Vexnum);
memset(Parent, 0, sizeof(int) * G.Vexnum);
int num = 1;
//需要遍历所有的边来寻找最小的n-1个
for (int i = 0; i < G.Arcnum; i++) {
//判断这条边会不会成环
int m = Find_Root(Parent, Edges[i].Begin), n = Find_Root(Parent, Edges[i].End);
//不会成环
if (m != n) {
//让大的做根
Parent[m] = n;
printf("第%d条弧依附的顶点为%c %c,权重为%d\n", num++, G.Vertices[Edges[i].Begin].Date, G.Vertices[Edges[i].End].Date, Edges[i].Weight);
}
}
}
int main() {
ALGraph G;
Creat_ALGraph(&G);
Mini_Span_Tree_Kruskal(G);
return 0;
}