前言:和Prim算法一样,Kruskal 算法也是用来生成最小生成树的,这篇文章来学习一下Kruskal算法的实现

一、实现流程

初始化的时候,将所有的边用一个数组存储,并且按权值从小到大进行排序,每次选一个权值最小的边加入到生成树中,但是在加入之前,需要判断加入的这条边会不会使生成树形成环路。接下来我们分步骤看一下算法的执行过程
我们来看这样一个图
在这里插入图片描述
1、边数组初始化
从小到大存储边
在这里插入图片描述

2、权值最小,为1的边连接4号节点和6号节点,这两个节点不在同一棵树中,不会形成环路,因此加入生成树
在这里插入图片描述
3、下一个权值最小的边,0-2 ,权值为2,这个边及加入生成树也不会形成环路,所以加入生成树
在这里插入图片描述
4、下一个权值最小的边是 5-7,同上也直接加入树
在这里插入图片描述
5、下一个边 0-2,同样加入生成树
在这里插入图片描述
6、下一个 0-3 的边,也不会形成环路,加入
在这里插入图片描述

7、下一个 3-5 的边也可以加入生成树
在这里插入图片描述

8、下一个 0-4 ,也可以直接加入
在这里插入图片描述

9、至此,所有的节点已经全部加入到生成树中,算法结束

二、代码实现

1、定义常数
#define MaxSize 100
#define MaxEdge 200  // 最大边数
#define MaxVex 100   // 最大顶点数
2、结构体

需要两个结构体,一个是图,一个是边
图需要包括点的个数,边的个数,和邻接矩阵

typedef struct {int vexnum;  // 顶点数int arcnum;  // 边数int arcs[MaxVex][MaxVex];  // 邻接矩阵
} MGraph;

边的结构体需要包含边的两个顶点,和边的权值

typedef struct{int a,b; // 边的两个顶点int weight; // 边的权值
} Edge
3、初始化变量和工具函数

需要一个边数组,存储所有的边

Edge edges[MaxEdge]; // 变数组

Kcuscal 算法需要找权值最小的边,所以对所有的边进行排序,c语言中有一个内置的 qsort 方法,可以对任何类型进行排序,接收四个参数:
参数一:void* base,待排数组
参数二:size_t num,待排元素的个数
参数三:size_t size,每个元素的大小,单位为字节,使用 sizeof() 函数获取
参数四:int (*compare)(const void * , const void *),排序函数
需要一个排序函数 compare,将权值比较小的边放在前面
void * 表示泛型指针类型,表示a可以指向任何类型的数据
(Edge*) 是一个类型转换,由于在入参中我们定义的 a 可以是任何类型的数据,这里使用 (Edge*) 将a转换为指向 Edge 结构体的指针。
Edge *edgeA 声明了一个新指针,并且将 a 指针的值赋值给它,现在 edgeA 就是一个指向 Edge 结构体的指针。
返回值,如果是负数,表示 edgeA->weight 小,则不更换数据的位置;如果是正数,表示 edgeA->weight 小,则要更换数据的位置。

int compare(const void *a, const void *b){Edge *edgeA = (Edge *) a;Edge *edgeB = (Edge *) b;return edgeA->weight - edgeB->weight;
}

另外判断边是否能够加入到生成树中的时候,需要判断这条边和生成树是不是在同一棵树中,如果在同一棵树中,那么加入这条边,一定会形成环路。我们在合并两个树的时候学过,将一个树 A 合并到另一棵树 B 中,就是将树的根节点 A 的父节点更新成另一棵树的根节点 B。
所以我们需要维护一个数组 parent,来存储所有的节点的父节点,初始化的时候,都初始化为-1

int parent[MaxVex]; // 根节点数组(并查集)

节点当加入到生成树中就需要进行合并操作,需要更新节点对应的根节点。
所以我们需要一个查找根节点的函数 Find,来查找一个节点所在的树的根节点
如果 parent[x] 小于 0 ,说明当前节点还没有加入到生成树中,就直接返回节点本身。如果parent[x] 大于 0,则向上找父节点,直到找到 parent[x] 小于 0 的的点。

// 并查集的Find操作
int Find(int *parent,int x){while(parent[x]>=0) x=parent[x]; // 循环向上寻找下标为x顶点的根return x; // while循环结束时找到了根的下标
}
4、主函数

在主函数中需要做的事情:
1、给边数组 edges 按权值排序
2、初始化 parent 数组为 -1
3、遍历边,找边的两个顶点的 parent,如果 parent 不相同,表示两个顶点不在同棵树中,则将其中一个顶点的 parent 指向另一个顶点,也就是将两个顶点合并在一棵树中

void MiniSpanTree_Kruskal(Gragh G){int i,n,m;// edges 排序qsort(edges, G.arcnum, sizeof(Edge), compare);// 初始化parentfor(i=0;i<G.vexnum;i++) parent[i] = -1;// 遍历所有边for(i=0;i<G.arcnum;i++){n = Find(edges[i].a);  // 第一个节点所在的树的根节点m = Find(edges[i].b);  // 第二个节点所在的树的根节点if(n!=m){  // 根节点不同,说明这两个节点位于两棵不同的树,则合并这两棵树parent[n] = m;printf("(%d->%d) 权值:%d\n", edges[i].a, edges[i].b, edges[i].weight);}}
}

三、和Prim算法的对比

组成树的元素有两个,一个是节点,一个是边。Prim 算法主要关注节点,找和当前的最小生成树距离最近的节点,把节点加入到生成树中。而Kruskal算法主要关注的是边,先将所有的边排序,将权值最小并且不会形成环路的边依次加入到生成树中。由于Kruskal算法要对边进行对比排序,所以Kruskal算法的执行效率取决于边的多少,适合边少的图,我们叫做稀疏图。而Prim算法主要关注节点,适合边多的图(边多就相当于节点少了),我们叫做稠密图。
下面我们来分析一下时间复杂度,假设图的节点数为 v ,边数为 e,
Prim 算法需要执行两层循环,每层执行的次数都是 v,所以Prim算法的时间复杂度是 O(v^2)。
Kruckal 算法 qsort()方法的时间复杂度是 O(elog2e),它是时间复杂度最高的方法,
外层需要遍历所有的边,时间复杂度是 O(e), Find 方法的并查集操作的时间复杂度可以优化到很小,可以忽略,所以整个算法的时间复杂度是 O(elog2e)

四、测试代码

在 main 函数中,需要初始化 edges 数组

// 克鲁斯卡尔算法
// 求最小生成树的算法
#include <stdio.h>
#include <stdlib.h>#define MaxSize 100
#define MaxEdge 200  // 最大边数
#define MaxVex 100   // 最大顶点数// 图的邻接矩阵表示
typedef struct {int vexnum;  // 顶点数int arcnum;  // 边数int arcs[MaxVex][MaxVex];  // 邻接矩阵
} MGraph;typedef struct {int a,b; // 边的两个顶点int weight; // 边的权值
}Edge;// 并查集的Find操作
int Find(int *parent,int x){while(parent[x]>=0) x=parent[x]; // 循环向上寻找下标为x顶点的根return x; // while循环结束时找到了根的下标
}// 比较函数,用于qsort排序
// const 放在*的左边,表示指针指向的数据不可变,但是指针的指向可变
int compare(const void *a, const void *b) {Edge *edgeA = (Edge*)a;Edge *edgeB = (Edge*)b;return edgeA->weight - edgeB->weight; // 按权值从小到大排序
}Edge edges[MaxEdge]; // 边数组
int parent[MaxVex]; // 父亲顶点数组(并查集)void MiniSpanTree_Kruskal(MGraph G){int i,n,m;// printf("\n排序前的边数组:\n");// for(i=0; i<G.arcnum; i++) {//     printf("(%d-%d) 权值:%d\n", edges[i].a, edges[i].b, edges[i].weight);// }// 按权值由小到大对边排列qsort(edges, G.arcnum, sizeof(Edge), compare);// printf("\n按权值排序后的边数组:\n");// for(i=0; i<G.arcnum; i++) {//     printf("(%d-%d) 权值:%d\n", edges[i].a, edges[i].b, edges[i].weight);// }for(i=0;i<G.vexnum;i++) parent[i]=-1; // 初始化并查集// printf("\n最小生成树的边:\n");for(i=0;i<G.arcnum;i++){ // 遍历每一条边n=Find(parent,edges[i].a); // n是这条边的第一个顶点的根节点所在的下标m=Find(parent,edges[i].b); // m是这条边的第二个顶点的根节点所在的下标if(n!=m){parent[n] = m; // 并操作printf("(%d->%d) 权值:%d\n", edges[i].a, edges[i].b, edges[i].weight);}}
}// 初始化图的边
void initGraph(MGraph *G) {// 示例图:6个顶点,9条边G->vexnum = 6;  // 顶点数 0-5G->arcnum = 9;  // 边数// 手动添加边的信息到edges数组// 顶点0-1,权值4edges[0].a = 0; edges[0].b = 1; edges[0].weight = 4;// 顶点0-2,权值6edges[1].a = 0; edges[1].b = 2; edges[1].weight = 6;// 顶点0-3,权值16edges[2].a = 0; edges[2].b = 3; edges[2].weight = 16;// 顶点1-2,权值10edges[3].a = 1; edges[3].b = 2; edges[3].weight = 10;// 顶点1-4,权值7edges[4].a = 1; edges[4].b = 4; edges[4].weight = 7;// 顶点2-3,权值14edges[5].a = 2; edges[5].b = 3; edges[5].weight = 14;// 顶点2-4,权值3edges[6].a = 2; edges[6].b = 4; edges[6].weight = 3;// 顶点2-5,权值8edges[7].a = 2; edges[7].b = 5; edges[7].weight = 8;// 顶点4-5,权值9edges[8].a = 4; edges[8].b = 5; edges[8].weight = 9;
}int main() {MGraph G;// printf("=== 克鲁斯卡尔最小生成树算法演示 ===\n\n");// 初始化图initGraph(&G);// printf("原始图的边信息:\n");// printf("顶点数:%d,边数:%d\n", G.vexnum, G.arcnum);// printf("所有边:\n");// for(int i = 0; i < G.arcnum; i++) {//     printf("(%d-%d) 权值:%d\n", edges[i].a, edges[i].b, edges[i].weight);// }// printf("\n开始执行克鲁斯卡尔算法:\n");// printf("================================\n");// 执行克鲁斯卡尔算法MiniSpanTree_Kruskal(G);// printf("================================\n");// printf("算法执行完成!\n");return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB 查询时区问题

MongoDB默认时区是UTC&#xff0c;比北京时区晚八小时&#xff0c;北京时间UTC8h。 // 北京时间的 2024-10-01 08:00:00 // (>) 大于 - $gt // (<) 小于 - $lt // (>) 大于等于 - $gte // (< ) 小于等于 - $lte// Z代表UTC时区1、{"gmtCreate":{"$…

Windows VS2019 编译 Apache Thrift 0.15.0

随着微服务架构的普及,高效的跨语言远程过程调用(RPC) 成为了构建分布式系统的重要基础。Apache Thrift 是 Facebook 开源的一个轻量级、高性能的 RPC 框架,它允许开发者通过一个通用的接口定义语言(IDL)来定义服务接口和数据结构,并自动生成多种语言的客户端和服务端代…

搭建种草商城框架指南

一、引言在当今电商市场&#xff0c;种草商城以其独特的社交化购物模式受到越来越多用户的喜爱。搭建一个功能完善、体验良好的种草商城框架&#xff0c;需要综合考虑前端界面、后端服务、数据库设计等多个方面。本文将为你详细介绍搭建种草商城框架的关键要点和技术选型。二、…

docker--挂载

设置容器的挂载 需要注意 挂载行为会覆盖容器目标目录的原有内容(未验证)。 查看容器的挂载情况 在容器外部查看: docker inspect <容器名或容器ID> | grep -A n "Mounts" -A n 的含义 -A 是 --after-context 的缩写,表示显示匹配行及其后 n 行。 "Mo…

以Streamable HTTP方式访问mcp server的过程

一、mcp server 部署 使用fastmcp框架 部署 mcp server&#xff0c; 以下是源代码 # 引入 fastmcp 依赖包 from fastmcp import FastMCP# 新建fastmcp实例&#xff0c; 名字叫做 weather mcp FastMCP("weather")mcp.tool(name"weather", tags{"weath…

二次元 IP 虚拟数字人宣传:漫画角色动态直播与衍生周边预售联动

当漫画角色从静态画稿中走出&#xff0c;以动态直播的形式与粉丝实时互动&#xff0c;再顺势开启衍生周边预售 —— 虚拟数字人技术正重塑二次元 IP 的宣传逻辑。这种 “动态直播 周边预售” 的联动模式&#xff0c;不仅打破了次元壁&#xff0c;更让 IP 热度高效转化为商业价…

如何在服务器上获取Linux目录大小

目前我在管理一台hostease的服务器时遇到服务器磁盘空间不足的情况。随着在系统中添加更多文件&#xff0c;这些系统文件目录也变得越来越大。过大的目录也消耗了系统资源&#xff0c;导致系统运行缓慢。后来我通过下列的方法对服务器上的磁盘空间使用进行了逐一检查。在这篇综…

来伊份养馋记社区零售 4.0 上海首店落沪:重构 “家门口” 的生活服务生态

7 月 19 日&#xff0c;来伊份与养馋记战略合作的首个 “社区零售 4.0” 门店在上海松江泗泾镇泗宝路正式开业。这不仅是双方自今年 1 月达成战略合作后的实质性落地&#xff0c;更是 3 月 “社区生活新生态” 构想的首次规模化实践&#xff0c;标志着零食行业巨头与社区零售新…

从C++开始的编程生活(3)——引用类型、内联inline和nullptr

前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第3篇主要讲的是有关于C的引用类型、内联inline和nullptr。 C才起步&#xff0c;都很简单呢&#xff01; 目录 前言 引用类型 基本语法 特性 应用 const引用 基本语法 引用与指针的关系 内联…

makefile-- 其他函数

fuctionsjoin​$(join <list1>,<list2>)连接函数把list2 中单词对应的添加到list1 的后面若list1 的单词个数> list2 &#xff0c;多出的list1 保持不变若list2 的单词个数> list21&#xff0c;多出的list2 添加到list1 后面foreach​$(foreach <var>…

【unity实战】使用unity的Navigation+LineRenderer实现一个3D人物寻路提前指示预测移动轨迹的效果,并可以适配不同的地形

文章目录 前言 实战 1、实现要点 1.1 NavMesh.CalculatePath方法计算两个点之间的导航路径 1.2 寻找投射的地面点 2、代码实现如下 3、烘培地面导航网格 4、添加导航玩家代理,并挂载前面的脚本 5、创建Line Renderer,并放在角色下面作为子物体 6、运行游戏查看效果 专栏推荐 …

宝塔申请证书错误,提示 module ‘OpenSSL.crypto‘ has no attribute ‘sign‘

遇到"module OpenSSL.crypto has no attribute sign"错误时&#xff0c;通常是由于pyOpenSSL版本兼容性问题导致的‌。以下是解决方案&#xff1a;通过SSH连接到服务器&#xff0c;执行以下命令安装指定版本的pyOpenSSL&#xff1a;btpip install pyOpenSSL24.2.1-U然…

【ffmpeg源码学习】详解pkg-config的作用

文章目录 前言 一、什么是pkg-config? 二、为什么需要 pkg-config? 三、pkg-config 的工作原理 3.1 .pc 文件 3.2 查询流程 3.3 查找路径 四、pkg-config 在 FFmpeg 中的作用 五、pkg-config 的常用命令 六、在项目中的实际用法 6.1 makefile示例: 6.2 cmake示例: 6.3 gcc命…

PHPStorm携手ThinkPHP8:开启高效开发之旅

目录一、前期准备1.1 开发环境搭建1.2 配置 Xdebug二、PHPStorm 集成 ThinkPHP82.1 导入 ThinkPHP8 项目2.2 配置 PHP 解释器2.3 配置服务器三、ThinkPHP8 项目开发基础3.1 项目结构剖析3.2 控制器与方法创建3.3 视图渲染与数据传递四、数据库操作与模型定义4.1 数据库配置4.2 …

HTTP性能优化实战技术详解(2025)

HTTP性能优化实战技术详解本文基于提供的文章大纲&#xff0c;对HTTP性能优化进行扩展说明。文章结构清晰&#xff0c;从理解瓶颈到具体优化策略&#xff0c;再到监控与高级技巧&#xff0c;逐步展开。每个部分包括背景介绍、核心原理、实施步骤、示例或工具推荐&#xff0c;确…

探索文件系统:软硬链接的奥秘

目录 1.文件系统 1.1 磁盘物理存储结构 1.2 磁盘逻辑存储结构 1.3 inode编号 2. 软硬链接 2.1 软链接 2.2 硬链接 2.3 目录文件的软硬链接 1.文件系统 在一台电脑中&#xff0c;大部分文件都不是被打开的&#xff0c;这些文件都在磁盘中进行保存。已经打开的文件需要管…

3x3矩阵教程

3x3矩阵教程 1. 简介 三维矩阵是线性代数中的重要概念&#xff0c;用于表示三维空间中的线性变换。本教程将介绍如何使用C实现三维矩阵的基本运算和变换。 2. 代码实现 2.1 头文件 (matrix3x3.h) #ifndef MATRIX3X3_H #define MATRIX3X3_H#include <array> #include <…

深度学习前置知识

文章目录介绍数据操作张量张量的定义1. **张量的维度&#xff08;Rank&#xff09;**2. **张量的形状&#xff08;Shape&#xff09;**简单的数据预处理&#xff08;插值线性代数微积分概率论1. 基本概念(1) 随机试验与事件(2) 概率公理&#xff08;Kolmogorov公理&#xff09;…

XSS学习总结

一.XSS概述 跨站脚本攻击&#xff08;Cross-Site Scripting&#xff0c;XSS&#xff09;是一种常见的网络安全漏洞&#xff0c;攻击者通过在网页上注入恶意脚本代码&#xff0c;从而在用户的浏览器上执行恶意操作。这些脚本可以是 JavaScript、HTML 或其他网页脚本语言。一旦用…

计算机网络中:传输层和网络层之间是如何配合的

可以把网络层和传输层想成一个“快递系统”&#xff1a; 网络层&#xff08;IP 层&#xff09; 邮政系统&#xff1a;只负责把“包裹”&#xff08;IP 数据报&#xff09;从 A 地搬到 B 地&#xff0c;不保证顺序、不保证不丢、不保证不重复。传输层&#xff08;TCP/UDP 层&am…