C语言-数据结构 无向图克鲁斯卡尔算法(Kruskal)邻接矩阵存储

news2024/12/24 10:11:13

        相比普里姆算法来说,克鲁斯卡尔的想法是从边出发,不管是理解上还是实现上都更简单,实现思路:我们先把找到所有边存到一个边集数组里面,并进行升序排序,然后依次从里面取出每一条边,如果不存在回路,就说明可以取,否则就跳过去看下一条边。其中看是否是回路这个操作利用到了并查集,就是判断新加入的这条边的两个顶点是否在同一个集合中,如果在就说明产生回路,如果没在同一个集合那么说明没有回路可以加入,这里只需要理解并查集中的查找、更新操作就行。并查集在上一篇博文里介绍过了,有需要的可以翻看,这里就不再具体介绍。

我们将创建下面的无向权值图:

84ef03cba4ba478383b338ae5884012a.png

  最小生成树示意图:

a60130ea5837483ba260f2b8a0ffa856.png

        邻接矩阵的绘制还是手动赋值上三角,并通过矩阵对称性生成整个邻接矩阵,其中最小生成树中需要用到权值,对应原本有边的地方之前我是用1表示,现在改成边对应的权值,之前的0表示没有边,现在改成99表示为无穷,其实应该换成更大的值以确保树的边权值都小于这个最大值,但为了方便对齐显示看邻接矩阵,就使用了比本图中各边长较大的99来表示最大值。

9eefaa5c866742cbb239f5f9de2aff7d.png

边集数组使用上三角矩阵生成,因为是无向图只需要取上三角或下三角就可以得到全部的边:

e5b3d526855e411bba85ad9d534ae6ca.png

获取边集数组代码:

// 从图的邻接矩阵中提取边,并按权重排序
Edge* GetEdges(MGraph G) {
	// 动态分配内存存储边
	Edge* Edges = (Edge*)malloc(sizeof(Edge) * G.numEdges);
	if (Edges == NULL) {
		printf("内存分配失败\n");
		exit(1);
	}

	int edgeIndex = 0;
	// 遍历邻接矩阵提取边
	for (int i = 0; i < G.numNodes; i++) {
		for (int j = i + 1; j < G.numNodes; j++) { // 从 i + 1 开始,避免重复的边
			if (G.arc[i][j] != 99) { // 99 表示没有边
				Edges[edgeIndex].begin = i;
				Edges[edgeIndex].end = j;
				Edges[edgeIndex].weight = G.arc[i][j];
				edgeIndex++;
			}
		}
	}

	// 按边的权重排序(冒泡排序)
	for (int i = 0; i < G.numEdges - 1; i++) {
		for (int j = 0; j < G.numEdges - i - 1; j++) {
			if (Edges[j].weight > Edges[j + 1].weight) {
				// 交换边
				Edge tmpEdge = Edges[j];
				Edges[j] = Edges[j + 1];
				Edges[j + 1] = tmpEdge;
			}
		}
	}

	return Edges;
}

        Kruskal算法代码:

// 查找节点所在集合的根
int Find(int parent[], int f) {
	while (parent[f] > 0) {
		f = parent[f];
	}
	return f;
}

// 使用 Kruskal 算法生成最小生成树
void MiniSpanTree_Kruskal(MGraph G, Edge Edges[]) {
	int i, n, m;
	Edge edges[MAXEDGE]; // 声明边数组(可能用于存储生成树的边)
	int parent[MAXVEX]; // 记录每个节点的父节点
	// 初始化每个节点的父节点为 0
	for (i = 0; i < G.numNodes; i++) {
		parent[i] = 0;
	}
	printf("\n");
	// 遍历每条边
	for (i = 0; i < G.numEdges; i++) {
		n = Find(parent, Edges[i].begin);
		m = Find(parent, Edges[i].end);
		if (n != m) {
			// 如果两个端点不在同一集合中,则将边加入最小生成树
			parent[n] = m;
			printf("第%d条边为:(%c,%c) %d\n", i + 1, Array[Edges[i].begin], Array[Edges[i].end], Edges[i].weight);
		}
		// 打印当前边处理后的 parent 数组
		printf("循环第%d条边后parent的值为:", i + 1);
		for (int j = 0; j < MAXVEX; j++) {
			printf("%d-", parent[j]);
		}
		printf("\n\n");
	}
}

完整代码(包含邻接矩阵的创建,边集的创建,Kruskal算法)

#include "stdio.h"    
#include "stdlib.h"   
#include "math.h"  
#include "time.h"

// 禁用特定的警告
#pragma warning(disable:4996)

// 定义一些常量和数据类型
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXVEX 8 /* 最大顶点数,用户定义 */
#define MAXEDGE 10 /* 最大边数,用户定义 */
#define GRAPH_INFINITY 99 /* 用0表示∞,表示不存在边 */

/* 定义状态、顶点和边的类型 */
typedef int Status;  /* Status是函数的返回类型,如OK表示成功 */
typedef char VertexType; /* 顶点的类型,用字符表示 */
typedef int EdgeType; /* 边上的权值类型,用整数表示 */
typedef int Boolean; /* 布尔类型 */
// 定义顶点标签
char Array[] = "ABCDEFGHI";
/* 访问标记数组 */
Boolean visited[MAXVEX];

/* 图的邻接矩阵结构体 */
typedef struct
{
	VertexType vexs[MAXVEX]; /* 顶点表 */
	EdgeType arc[MAXVEX][MAXVEX]; /* 邻接矩阵,表示边的权值 */
	int numNodes, numEdges; /* 图中当前的顶点数和边数 */
} MGraph;

/* 边集数组结构体,用于辅助克鲁斯卡尔算法 */
typedef struct {
	int begin;  // 边的起始顶点
	int end;    // 边的终止顶点
	int weight; // 边的权重
} Edge;

/* 创建一个无向网图的邻接矩阵表示 */
void CreateMGraph(MGraph* G)
{
	int i, j, k, w;

	// 初始化图的顶点数和边数
	G->numNodes = 8;
	G->numEdges = 10;

	

	// 初始化邻接矩阵和顶点表
	for (i = 0; i < G->numNodes; i++) {
		for (j = 0; j < G->numNodes; j++) {
			G->arc[i][j] = GRAPH_INFINITY; /* 初始化邻接矩阵为∞ */
		}
		G->vexs[i] = Array[i]; /* 初始化顶点表 */
	}

	G->arc[0][0] = GRAPH_INFINITY;
	G->arc[0][1] = 10;
	G->arc[0][2] = GRAPH_INFINITY;
	G->arc[0][3] = GRAPH_INFINITY;
	G->arc[0][4] = GRAPH_INFINITY;
	G->arc[0][5] = 11;
	G->arc[0][6] = GRAPH_INFINITY;
	G->arc[0][7] = GRAPH_INFINITY;

	G->arc[1][0] = GRAPH_INFINITY;
	G->arc[1][1] = GRAPH_INFINITY;
	G->arc[1][2] = 23;
	G->arc[1][3] = GRAPH_INFINITY;
	G->arc[1][4] = GRAPH_INFINITY;
	G->arc[1][5] = GRAPH_INFINITY;
	G->arc[1][6] = 12;
	G->arc[1][7] = GRAPH_INFINITY;

	G->arc[2][0] = GRAPH_INFINITY;
	G->arc[2][1] = GRAPH_INFINITY;
	G->arc[2][2] = GRAPH_INFINITY;
	G->arc[2][3] = 21;
	G->arc[2][4] = GRAPH_INFINITY;
	G->arc[2][5] = GRAPH_INFINITY;
	G->arc[2][6] = GRAPH_INFINITY;
	G->arc[2][7] = GRAPH_INFINITY;

	G->arc[3][0] = GRAPH_INFINITY;
	G->arc[3][1] = GRAPH_INFINITY;
	G->arc[3][2] = GRAPH_INFINITY;
	G->arc[3][3] = GRAPH_INFINITY;
	G->arc[3][4] = GRAPH_INFINITY;
	G->arc[3][5] = GRAPH_INFINITY;
	G->arc[3][6] = GRAPH_INFINITY;
	G->arc[3][7] = 11;

	G->arc[4][0] = GRAPH_INFINITY;
	G->arc[4][1] = GRAPH_INFINITY;
	G->arc[4][2] = GRAPH_INFINITY;
	G->arc[4][3] = GRAPH_INFINITY;
	G->arc[4][4] = GRAPH_INFINITY;
	G->arc[4][5] = 47;
	G->arc[4][6] = GRAPH_INFINITY;
	G->arc[4][7] = 80;

	G->arc[5][0] = GRAPH_INFINITY;
	G->arc[5][1] = GRAPH_INFINITY;
	G->arc[5][2] = GRAPH_INFINITY;
	G->arc[5][3] = GRAPH_INFINITY;
	G->arc[5][4] = GRAPH_INFINITY;
	G->arc[5][5] = GRAPH_INFINITY;
	G->arc[5][6] = 6;
	G->arc[5][7] = GRAPH_INFINITY;

	G->arc[6][0] = GRAPH_INFINITY;
	G->arc[6][1] = GRAPH_INFINITY;
	G->arc[6][2] = GRAPH_INFINITY;
	G->arc[6][3] = GRAPH_INFINITY;
	G->arc[6][4] = GRAPH_INFINITY;
	G->arc[6][5] = GRAPH_INFINITY;
	G->arc[6][6] = GRAPH_INFINITY;
	G->arc[6][7] = 8;

	G->arc[7][0] = GRAPH_INFINITY;
	G->arc[7][1] = GRAPH_INFINITY;
	G->arc[7][2] = GRAPH_INFINITY;
	G->arc[7][3] = GRAPH_INFINITY;
	G->arc[7][4] = GRAPH_INFINITY;
	G->arc[7][5] = GRAPH_INFINITY;
	G->arc[7][6] = GRAPH_INFINITY;
	G->arc[7][7] = GRAPH_INFINITY;

	// 由于是无向图,邻接矩阵是对称的,需要将其对称
	for (int i = 0; i < G->numNodes; i++) {
		for (int j = 0; j < G->numNodes; j++) {
			G->arc[j][i] = G->arc[i][j];
		}
	}

	// 打印邻接矩阵
	printf("邻接矩阵为:\n");
	printf("     ");
	for (int i = 0; i < G->numNodes; i++) {
		printf("%2d ", i); /* 打印列索引 */
	}
	printf("\n     ");
	for (int i = 0; i < G->numNodes; i++) {
		printf("%2c ", G->vexs[i]); /* 打印顶点标签 */
	}
	printf("\n");
	for (int i = 0; i < G->numNodes; i++) {
		printf("%2d", i); /* 打印行索引 */
		printf("%2c ", G->vexs[i]); /* 打印顶点标签 */
		for (int j = 0; j < G->numNodes; j++) {
			if (G->arc[i][j] != 99) {
				printf("\033[31m%02d \033[0m", G->arc[i][j]); /* 打印邻接矩阵中的权值 */
			}
			else {
				printf("%02d ", G->arc[i][j]); /* 打印邻接矩阵中的权值 */
			}
		}
		printf("\n");
	}
}

// 从图的邻接矩阵中提取边,并按权重排序
Edge* GetEdges(MGraph G) {
	// 动态分配内存存储边
	Edge* Edges = (Edge*)malloc(sizeof(Edge) * G.numEdges);
	if (Edges == NULL) {
		printf("内存分配失败\n");
		exit(1);
	}

	int edgeIndex = 0;
	// 遍历邻接矩阵提取边
	for (int i = 0; i < G.numNodes; i++) {
		for (int j = i + 1; j < G.numNodes; j++) { // 从 i + 1 开始,避免重复的边
			if (G.arc[i][j] != 99) { // 99 表示没有边
				Edges[edgeIndex].begin = i;
				Edges[edgeIndex].end = j;
				Edges[edgeIndex].weight = G.arc[i][j];
				edgeIndex++;
			}
		}
	}

	// 按边的权重排序(冒泡排序)
	for (int i = 0; i < G.numEdges - 1; i++) {
		for (int j = 0; j < G.numEdges - i - 1; j++) {
			if (Edges[j].weight > Edges[j + 1].weight) {
				// 交换边
				Edge tmpEdge = Edges[j];
				Edges[j] = Edges[j + 1];
				Edges[j + 1] = tmpEdge;
			}
		}
	}

	return Edges;
}

// 打印边集数组
void EdgesPrint(Edge Edges[], int EdgeNumber) {
	// 打印标题行
	printf("\n\t边集数组\n");
	printf("\t  %-5s  %-5s %-7s\n", "begin", "end", "weight");
	// 打印每条边的详细信息
	for (int i = 0; i < EdgeNumber; i++) {
		printf("Edge[%d]   (%c)%-5d (%c)%-5d %-7d\n", i, Array[Edges[i].begin], Edges[i].begin, Array[Edges[i].end], Edges[i].end, Edges[i].weight);
	}
}

// 查找节点所在集合的根
int Find(int parent[], int f) {
	while (parent[f] > 0) {
		f = parent[f];
	}
	return f;
}

// 使用 Kruskal 算法生成最小生成树
void MiniSpanTree_Kruskal(MGraph G, Edge Edges[]) {
	int i, n, m;
	Edge edges[MAXEDGE]; // 声明边数组(可能用于存储生成树的边)
	int parent[MAXVEX]; // 记录每个节点的父节点
	// 初始化每个节点的父节点为 0
	for (i = 0; i < G.numNodes; i++) {
		parent[i] = 0;
	}
	printf("\n");
	// 遍历每条边
	for (i = 0; i < G.numEdges; i++) {
		n = Find(parent, Edges[i].begin);
		m = Find(parent, Edges[i].end);
		if (n != m) {
			// 如果两个端点不在同一集合中,则将边加入最小生成树
			parent[n] = m;
			printf("第%d条边为:(%c,%c) %d\n", i + 1, Array[Edges[i].begin], Array[Edges[i].end], Edges[i].weight);
		}
		// 打印当前边处理后的 parent 数组
		printf("循环第%d条边后parent的值为:", i + 1);
		for (int j = 0; j < MAXVEX; j++) {
			printf("%d-", parent[j]);
		}
		printf("\n\n");
	}
}

int main(void)
{
	MGraph G;
	/* 创建图 */
	CreateMGraph(&G);
	//获取边集数组
	Edge * Edges =GetEdges(G);
	//打印边集数组
	EdgesPrint(Edges,G.numEdges);
	//使用克鲁斯卡尔算法
	MiniSpanTree_Kruskal(G, Edges);
	return 0;
}

运行结果(标黄色的就是没有加入最小生成树的边,即产生了回路):

最小生成树示意图

a60130ea5837483ba260f2b8a0ffa856.png

8cdc61d98e2847d2b2f79ac6a7cfb6cb.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2136072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python vtk 绘制圆柱体和包围盒

基本的代码如下&#xff0c; import vtkcylinder vtk.vtkCylinderSource() cylinder.SetRadius(3.0) cylinder.SetHeight(10.0) cylinder.SetResolution(50)boundsFilter vtk.vtkOutlineFilter() boundsFilter.SetInputConnection(cylinder.GetOutputPort())mapper vtk.vtk…

UQpy | 不确定性量化Python工具箱推荐

UQpy, "Uncertainty Quantification with Python,"是一个通用的 Python 工具箱&#xff0c;用于对物理和数学系统模拟中的不确定性进行建模。该代码被组织为一组以不确定性量化&#xff08;UQ&#xff09;的核心功能为中心的模块&#xff0c;如下所示。这些模块各不相…

无线安全(WiFi)

免责声明:本文仅做分享!!! 目录 WEP简介 WPA简介 安全类型 密钥交换 PMK PTK 4次握手 WPA攻击原理 网卡选购 攻击姿态 1-暴力破解 脚本工具 字典 2-Airgeddon 破解 3-KRACK漏洞 4-Rough AP 攻击 5-wifi钓鱼 6-wifite 其他 WEP简介 WEP是WiredEquivalentPri…

I/O 多路复用:`select`、`poll`、`epoll` 和 `kqueue` 的区别与示例

I/O 多路复用是指在一个线程内同时监控多个文件描述符&#xff08;File Descriptor, FD&#xff09;&#xff0c;以便高效地处理多个 I/O 事件。在 UNIX/Linux 和 BSD 系统中&#xff0c;select、poll、epoll、kqueue 都是实现 I/O 多路复用的系统调用。它们各有特点&#xff0…

MYMPay码支付开源版系统源码

MYMPay码支付开源版系统源码 前言安装环境&#xff1a;首页图片用户中心管理后台 部分源码领取源码下期更新 前言 最新版MYMPay码支付开源版系统源码_个人免签支付_聚合支付系统 安装环境&#xff1a; PHP&#xff1a;7.0-8.2 (推荐使用7.4)需要安装Xload 扩展MySQL&#xf…

Linux基础入门篇

一.Linux概述 我们一般所说的Liunx表示的是Linux的内核部分&#xff0c;Liunx 发行版是在其内核的基础上进行了对其他软件的集成&#xff0c;更加方便了用户的使用 Liunx的结构&#xff1a; 目前市场上使用的Linux大多为CenterOS,一些微型的开发中会使用到Ubuntu,两者在一些指…

【刷题】Day4--密码检查

Hi&#xff01; 今日刷题&#xff0c;小白一枚&#xff0c;欢迎指导 ~ 【链接】 密码检查_牛客题霸_牛客网 【思路】 依次根据规则判断密码是否合格。while里嵌套个for循环&#xff0c;来进行密码的多组输入&#xff0c;for循环进行一次代表判断一个密码串&#xff1b;规则…

STM32+ESP01连接到机智云

机智云,全球领先的智能硬件软件自助开发及物联网(iot)云服务平台。机智云平台为开发者提供了自助式智能硬件开发工具与开放的云端服务。通过傻瓜化的自助工具、完善的SDK与API服务能力最大限度降低了物联网硬件开发的技术门槛&#xff0c;降低开发者的研发成本&#xff0c;提升…

添加选择登录ssh终端

吼吼,这次成了一个小的瑞士军刀了 … …

2.Jmeter安装配置,核心目录详情,组件和作用域

一、Jmeter安装配置以及核心目录详情 Jmeter基于java语言来开发&#xff0c;java需要jdk环境。 1.安装jdk并且配置jdk的环境变量。 2.jmeter只需要解压就可以使用了。 3.在D:\apache-jmeter-5.5\bin目录下双击jmeter.bat文件就可以启动使用了 backups&#xff1a;自动备份的目录…

OpenHarmony(鸿蒙南向开发)——轻量系统STM32F407芯片移植案例

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——轻量和小型系统三方库移植指南…

JAVA毕业设计170—基于Java+Springboot+vue3+小程序的房屋租赁小程序系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3小程序的房屋租赁小程序系统(源代码数据库)170 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、房东、管理员三种角色 1、用户&am…

华为云分布式缓存服务DCS 8月新特性发布

分布式缓存服务&#xff08;Distributed Cache Service&#xff0c;简称DCS&#xff09;是华为云提供的一款兼容Redis的高速内存数据处理引擎&#xff0c;为您提供即开即用、安全可靠、弹性扩容、便捷管理的在线分布式缓存能力&#xff0c;满足用户高并发及数据快速访问的业务诉…

[PICO VR眼镜]眼动追踪串流Unity开发与使用方法,眼动追踪打包报错问题解决(Eye Tracking)

前言 最近在做一个工作需要用到PICO4 Enterprise VR头盔里的眼动追踪功能&#xff0c;但是遇到了如下问题&#xff1a; 在Unity里面没法串流调试眼动追踪功能&#xff0c;根本获取不到Device&#xff0c;只能将整个场景build成APK&#xff0c;安装到头盔里&#xff0c;才能在…

明星代言推广的6种优势,打造品牌巅峰!

在当今的商业社会中&#xff0c;品牌推广成为了企业发展不可或缺的一环。对于许多企业来说&#xff0c;明星代言已经成为了一种常见的推广策略。本文将介绍明星代言推广的六种优势&#xff0c;帮助企业了解并充分利用这一策略&#xff0c;从而打造品牌的巅峰&#xff01; 1. 塑…

java中SPI(服务提供者的接口)

java中SPI&#xff08;服务提供者的接口&#xff09; 一&#xff1a;什么是SPI二&#xff1a;java SPI示例1.SPI服务提供方2.SPI服务应用方开发者 三&#xff1a;JavaSPI 机制的核心-ServiceLoader 一&#xff1a;什么是SPI SPI&#xff1a;“服务提供者的接口”&#xff0c;是…

USART—串口数据包

1.HEX数据包定义 数据包的作用是把一个个单独的数据给打包起来&#xff0c;方便我们进行多字节的数据通信&#xff0c;在实际应用中&#xff0c;我们可能需要把多个字节打包为一个整体进行发送&#xff0c;比如说&#xff0c;我们有个陀螺仪传感器&#xff0c;需要用串口发送数…

git 你要如何打开这个文件

终端输入git命令都会弹出这个框 解决方案&#xff1a; 参考文章&#xff1a;在vscode终端上运行 npm 会询问 “你要如何打开这个文件“_安装 npm脚手架提示你要如何打开-CSDN博客 get-command git后删除对应文件 请注意不要删错了&#xff0c;正常get-command git后对应的是…

微型导轨加工环境需避免的隐患!

微型导轨是一种小巧精密的线性定位解决方案&#xff0c;其高速度、低噪音的特点使得它在现代制造业中扮演着越来越重要的角色。而微型导轨对于加工环境的要求主要体现在以下几个方面&#xff1a; 1、温度控制&#xff1a;加工环境需要保持在适宜的温度范围内&#xff0c;过高或…

【西电电装实习】5. 无人机模块及作用、上位机的操作

文章目录 前言一、硬件结构电源、电源电压测试电路晶振外围陀螺仪信号放大电路及天线空心杯&#xff08;电极&#xff09;驱动电路 软件设置整机装配PID 参数设置公式 参考文献 前言 西电电装实习&#xff0c;无人机原理图、上位机的调节方法 一、硬件结构 电源、电源电压测…