【数据结构——最小生成树与Kruskal】

引入

最小生成树 (Minimum Spanning Tree, MST)是无向连通图 中一棵包含所有顶点 的生成树,且其边的权值之和最小

  • 最小生成树的性质
    连通性:包含图中所有顶点。
    无环性:是一棵树,不包含任何环路。
    最小权值和:所有边的权值总和在所有可能的生成树中最小。

简单来说就是:无向 连通全部顶点不成环 ,顶点n个边(n-1)个,权和最低。

注意:最小生成树的结构不唯一

加边细则

  • 选边要求:优先加权值最小的边,直到加够(n-1)条边为止。
  • 加边要求:加上边不成环。
  • 判断是否成环(验证):
  1. 边与边通过顶点形成一条连线,将此线经由的所有顶点都看成是一个集合的
  2. 集合与集合之间若不相通,则两集合之间可以添加一条边(每次添加一条)
  • 判断加边后是否成环(加边判断):
    利用并查集(quickUnion)的思想(回顾quickUnion):找每个节点的根节点,同根不连(不加边)。

实现

首先为了方便存储无向图的权值,图用邻接矩阵表示;其次利用邻接矩阵创建边集数组;最后是利用边集数组完成Kruskal算法的实现。

实现思路:

  • 进行加边操作前要以权值大小为标准从小到大排序
  • 依次判断加上边是否成环:
    不成的话这条边就归入生成树的组成部分;
    成环就再找下一条边再次判断。

头文件

  • common.h
c 复制代码
//边集数组结构
typedef struct {
	int begin;   //起点
	int end;     //终点
	int weight;  //权值
}EdgeSet;
  • Kruskal.h
c 复制代码
#include"common.h"
#include"matrixGraph.h"

//从邻接矩阵中初始化边集数组
void InitEdgeSet(const MGraph* graph, EdgeSet* edges);

//排序边集数组
void sortEdgeSet(EdgeSet* edges, int num);

//Kruskal算法
int KruskalMGraph(const EdgeSet* edges, int num, int node_num, EdgeSet* result);

功能实现

补充:
memcpy 与 strcpy 的区别

  • 功能差异

    memcpy 用于复制任意内存块,不关心数据内容(如字符串、结构体、数组等)。strcpy 专门用于复制以 \0 结尾的字符串,遇到 \0 自动停止。

  • 参数差异

    memcpy(void* dest, const void* src, size_t n):需指定复制的字节数 n。

    strcpy(char* dest, const char* src):无需指定长度,自动根据 \0 判断结束。

  • 安全性

    memcpy 需手动确保目标缓冲区足够大,否则可能溢出。strcpy 同样存在溢出风险,但更易因缺失 \0 导致问题。

#include"Kruskal.h"

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

初始化边集数组
c 复制代码
void InitEdgeSet(const MGraph* graph, EdgeSet* edges) {
	int k = 0;
	for (int i = 0; i < graph->nodeNum; ++i) {
		for (int j = i + 1; j < graph->nodeNum; ++j) {
			if (graph->edge[i][j] > 0) {
				edges[k].begin = i;
				edges[k].end = j;
				edges[k].weight = graph->edge[i][j];
				k++;
			}
		}
	}
}
边集数组排序
c 复制代码
void sortEdgeSet(EdgeSet* edges, int num) {
	EdgeSet tmp;
	for (int i = 0; i < num; ++i) {
		for (int j = i + 1; j < num; ++j) {
			if (edges[j].weight < edges[i].weight) {
				memcpy(&tmp, &edges[i], sizeof(EdgeSet));
				memcpy(&edges[i], &edges[j], sizeof(EdgeSet));
				memcpy(&edges[j], &tmp, sizeof(EdgeSet));
			}
		}
	}
}

代码通过循环不断向上追溯父节点,直到找到根节点:

初始化时传入参数 a,表示需要查找根节点的元素。

检查当前元素 a 的父节点是否等于自身。若不等,则将 a 更新为其父节点,继续循环。

当 uSet[a] == a 时,说明已到达根节点,退出循环并返回 a。

找根
c 复制代码
static int getRoot(const int* uSet, int a) {
	if (uSet == NULL) {
		printf("malloc failed!\n");
		return -1;
	}
	while (uSet[a] != a) {
		a = uSet[a];
	}
	// 退出循环时,uSet[a] == a,即 a 是根节点
	return a;
}
Kruskal实现
c 复制代码
int KruskalMGraph(const EdgeSet* edges, int num, int node_num, EdgeSet* result) {
	int* uSet;
	int count = 0;
	int sum = 0;
	//1.初始化并查集
	uSet =(int*) malloc(sizeof(int) * node_num);
	if (uSet == NULL) {
		printf("malloc failed!\n");
		return -1;
	}
	for (int i = 0; i < node_num; ++i) {
		uSet[i] = i;
	}
	//2.从已经排列好的边集中找边的起始节点的根,判断是否成环
	for (int i = 0; i < num; ++i) {
		int a = getRoot(uSet, edges[i].begin);
		int b = getRoot(uSet, edges[i].end);
		if (a != b) {
			uSet[a] = b; //将a的父节点设为b
			result[count].begin = edges[i].begin;
			result[count].end = edges[i].end;
			result[count].weight = edges[i].weight;
			sum += edges[i].weight;
			count++;
			if (count == node_num - 1) {
				break;
			}
		}
	}
	free(uSet);
	return sum;
}

main函数

图的创建
c 复制代码
void setupMGraph(MGraph* graph) {
	//                  0    1    2    3    4    5    6
	const char* names[] = {"A", "B", "C", "D", "E", "F", "G"};
	initMGraph(graph, names, sizeof(names) / sizeof(names[0]), 0, 0);

	addMGraphEdge(graph, 0, 1, 12);
	addMGraphEdge(graph, 0, 5, 16);
	addMGraphEdge(graph, 0, 6, 14);
	addMGraphEdge(graph, 1, 2, 10);
	addMGraphEdge(graph, 1, 5, 7);
	addMGraphEdge(graph, 2, 3, 3);
	addMGraphEdge(graph, 2, 4, 5);
	addMGraphEdge(graph, 2, 5, 6);
	addMGraphEdge(graph, 3, 4, 4);
	addMGraphEdge(graph, 4, 5, 2);
	addMGraphEdge(graph, 4, 6, 8);
	addMGraphEdge(graph, 5, 6, 9);
}
功能调用
c 复制代码
void test251014() {
	MGraph graph;
	EdgeSet* edges;
	EdgeSet* result;

	setupMGraph(&graph);
	edges = (EdgeSet*)malloc(sizeof(EdgeSet) * graph.edgeNum);
	if (edges == NULL) {
		return;
	}
	InitEdgeSet(&graph, edges);
	sortEdgeSet(edges, graph.edgeNum);

	result = (EdgeSet*)malloc(sizeof(EdgeSet) * (graph.nodeNum - 1));
	if (result == NULL) {
		//edges是先前malloc分配的边集数组,属于中间结果。
		// 若后续关键内存分配(如result)失败,整个操作无法完成,中间结果便失去意义。
		// 释放edges符合"申请失败即回滚"的防御性编程原则。
		free(edges);   //回滚
		return;
	}
	int sum = KruskalMGraph(edges, graph.edgeNum, graph.nodeNum, result);
	for (int i = 0; i < graph.nodeNum - 1; i++) {
		printf("edge %d: <%s> --- <%d> ---- <%s>\n", i + 1,
			graph.vex[result[i].begin].show, result[i].weight, graph.vex[result[i].end].show);
	}
	printf("sum = %d\n", sum);
	free(result);
	free(edges);
}
c 复制代码
int main() {
	test251014();
	return 0;
}
相关推荐
im_AMBER2 小时前
算法笔记 09
c语言·数据结构·c++·笔记·学习·算法·排序算法
凯芸呢2 小时前
Java中的数组(续)
java·开发语言·数据结构·算法·青少年编程·排序算法·idea
寂静山林3 小时前
UVa 1030 Image Is Everything
算法
AI柠檬3 小时前
几种排序算法的实现和性能比较
数据结构·算法·c#·排序算法
weixin_429630263 小时前
第6章 支持向量机
算法·机器学习·支持向量机
SweetCode3 小时前
C++ 实现大数加法
开发语言·c++·算法
王哈哈^_^3 小时前
【数据集】【YOLO】【目标检测】共享单车数据集,共享单车识别数据集 3596 张,YOLO自行车识别算法实战训推教程。
人工智能·算法·yolo·目标检测·计算机视觉·视觉检测·毕业设计
CodeWizard~4 小时前
AtCoder Beginner Contest 430赛后补题
c++·算法·图论
大大dxy大大4 小时前
机器学习-KNN算法示例
人工智能·算法·机器学习
zz0723205 小时前
数据结构 —— 栈
数据结构