Kruskal算法剖析与py/cpp/Java语言实现

Kruskal算法剖析与py/cpp/Java语言实现

    • 一、Kruskal算法的基本概念
      • 1.1 最小生成树
      • 1.2 Kruskal算法核心思想
    • 二、Kruskal算法的执行流程
    • 三、Kruskal算法的代码实现
      • 3.1 Python实现
      • 3.2 C++实现
      • 3.3 Java实现
    • 四、算法复杂度分析
      • 4.1 时间复杂度
      • 4.2 空间复杂度
    • 五、Kruskal算法应用场景
      • 5.1 网络布线
      • 5.2 聚类分析
      • 5.3 电路设计
    • 总结

图论算法中最小生成树(Minimum Spanning Tree,MST)问题是一个经典且具有重要实际意义的问题。Kruskal算法作为求解最小生成树的常用算法之一,以其简洁的思想和高效的实现,在网络规划、电路设计、聚类分析等众多领域发挥着关键作用。本文我将深入剖析Kruskal算法的原理、详细介绍其执行流程,并分别使用Python、C++和Java三种编程语言进行代码实现,帮你全面掌握这一经典算法。

一、Kruskal算法的基本概念

1.1 最小生成树

对于一个连通无向图 (G=(V, E)),其中 (V) 是顶点集合,(E) 是边集合,其最小生成树是一个包含图中所有顶点的树状子图 (T=(V, E’))((E’ \subseteq E)),并且满足边的权值之和最小。最小生成树具有以下特点:

  • 包含图中的所有顶点,即顶点数为 (|V|)。
  • 边数为 (|V| - 1),因为树的边数比顶点数少 1。
  • 不存在回路(环),确保其树状结构。
  • 边的权值总和在所有满足上述条件的子图中最小。

1.2 Kruskal算法核心思想

Kruskal算法基于贪心策略,其核心思想是:从图中所有边中选择权值最小的边,若该边的两个顶点不在同一个连通分量中,则将这条边加入到最小生成树中;重复这个过程,直到最小生成树包含图中的所有顶点,或者边的数量达到 (|V| - 1) 为止。通过不断选择局部最优(权值最小且不构成回路的边),最终得到全局最优的最小生成树。

二、Kruskal算法的执行流程

  1. 初始化:将图中的每条边按照权值从小到大进行排序。同时,初始化一个用于存储最小生成树的边集合 mst_edges,以及一个用于判断顶点是否在同一个连通分量的并查集数据结构(可以使用数组或更复杂的并查集类实现)。
  2. 遍历边:依次遍历排序后的边集合,对于每条边 ((u, v, w))(其中 (u) 和 (v) 是边的两个顶点,(w) 是边的权值):
    • 使用并查集判断顶点 (u) 和 (v) 是否在同一个连通分量中。如果不在同一个连通分量,说明将这条边加入最小生成树不会形成回路,则将该边加入到 mst_edges 中,并通过并查集将 (u) 和 (v) 所在的连通分量合并。
    • 如果顶点 (u) 和 (v) 已经在同一个连通分量中,说明加入这条边会形成回路,直接跳过该边。
  3. 结束条件:当最小生成树的边数达到 (|V| - 1) 时,或者遍历完所有边后,算法结束。此时,mst_edges 中存储的边集合即为图的最小生成树。
    Kruskal

三、Kruskal算法的代码实现

3.1 Python实现

def find(parent, i):if parent[i] == i:return ireturn find(parent, parent[i])def union(parent, rank, x, y):xroot = find(parent, x)yroot = find(parent, y)if rank[xroot] < rank[yroot]:parent[xroot] = yrootelif rank[xroot] > rank[yroot]:parent[yroot] = xrootelse:parent[yroot] = xrootrank[xroot] += 1def kruskalMST(graph):result = []i, e = 0, 0edges = []for u in range(len(graph)):for v, w in enumerate(graph[u]):if w > 0:edges.append((u, v, w))edges.sort(key=lambda item: item[2])parent = []rank = []for v in range(len(graph)):parent.append(v)rank.append(0)while e < len(graph) - 1 and i < len(edges):u, v, w = edges[i]i = i + 1x = find(parent, u)y = find(parent, v)if x != y:e = e + 1result.append((u, v, w))union(parent, rank, x, y)return resultgraph = [[0, 10, 6, 5, 0, 0],[10, 0, 0, 15, 0, 0],[6, 0, 0, 4, 7, 0],[5, 15, 4, 0, 0, 8],[0, 0, 7, 0, 0, 9],[0, 0, 0, 8, 9, 0]
]
print(kruskalMST(graph))

3.2 C++实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;class Edge {
public:int src, dest, weight;Edge(int s, int d, int w) : src(s), dest(d), weight(w) {}
};bool compareEdges(const Edge& a, const Edge& b) {return a.weight < b.weight;
}int find(vector<int>& parent, int i) {if (parent[i] == i)return i;return find(parent, parent[i]);
}void unionSet(vector<int>& parent, vector<int>& rank, int x, int y) {int xroot = find(parent, x);int yroot = find(parent, y);if (rank[xroot] < rank[yroot])parent[xroot] = yroot;else if (rank[xroot] > rank[yroot])parent[yroot] = xroot;else {parent[yroot] = xroot;rank[xroot]++;}
}vector<Edge> kruskalMST(vector<vector<int>>& graph) {vector<Edge> edges;for (int u = 0; u < graph.size(); u++) {for (int v = 0; v < graph.size(); v++) {if (graph[u][v] > 0) {edges.push_back(Edge(u, v, graph[u][v]));}}}sort(edges.begin(), edges.end(), compareEdges);vector<int> parent(graph.size());vector<int> rank(graph.size(), 0);for (int i = 0; i < graph.size(); i++) {parent[i] = i;}vector<Edge> result;int e = 0, i = 0;while (e < graph.size() - 1 && i < edges.size()) {Edge next_edge = edges[i++];int x = find(parent, next_edge.src);int y = find(parent, next_edge.dest);if (x != y) {e++;result.push_back(next_edge);unionSet(parent, rank, x, y);}}return result;
}int main() {vector<vector<int>> graph = {{0, 10, 6, 5, 0, 0},{10, 0, 0, 15, 0, 0},{6, 0, 0, 4, 7, 0},{5, 15, 4, 0, 0, 8},{0, 0, 7, 0, 0, 9},{0, 0, 0, 8, 9, 0}};vector<Edge> mst = kruskalMST(graph);for (const auto& edge : mst) {cout << edge.src << " - " << edge.dest << " : " << edge.weight << endl;}return 0;
}

3.3 Java实现

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;class Edge implements Comparable<Edge> {int src, dest, weight;public Edge(int src, int dest, int weight) {this.src = src;this.dest = dest;this.weight = weight;}@Overridepublic int compareTo(Edge other) {return Integer.compare(this.weight, other.weight);}
}class Kruskal {static int find(int[] parent, int i) {if (parent[i] == i)return i;return find(parent, parent[i]);}static void union(int[] parent, int[] rank, int x, int y) {int xroot = find(parent, x);int yroot = find(parent, y);if (rank[xroot] < rank[yroot])parent[xroot] = yroot;else if (rank[xroot] > rank[yroot])parent[yroot] = xroot;else {parent[yroot] = xroot;rank[xroot]++;}}static List<Edge> kruskalMST(int[][] graph) {List<Edge> edges = new ArrayList<>();for (int u = 0; u < graph.length; u++) {for (int v = 0; v < graph.length; v++) {if (graph[u][v] > 0) {edges.add(new Edge(u, v, graph[u][v]));}}}Collections.sort(edges);int[] parent = new int[graph.length];int[] rank = new int[graph.length];for (int i = 0; i < graph.length; i++) {parent[i] = i;}List<Edge> result = new ArrayList<>();int e = 0, i = 0;while (e < graph.length - 1 && i < edges.size()) {Edge nextEdge = edges.get(i++);int x = find(parent, nextEdge.src);int y = find(parent, nextEdge.dest);if (x != y) {e++;result.add(nextEdge);union(parent, rank, x, y);}}return result;}
}public class KruskalAlgorithm {public static void main(String[] args) {int[][] graph = {{0, 10, 6, 5, 0, 0},{10, 0, 0, 15, 0, 0},{6, 0, 0, 4, 7, 0},{5, 15, 4, 0, 0, 8},{0, 0, 7, 0, 0, 9},{0, 0, 0, 8, 9, 0}};List<Edge> mst = Kruskal.kruskalMST(graph);for (Edge edge : mst) {System.out.println(edge.src + " - " + edge.dest + " : " + edge.weight);}}
}

四、算法复杂度分析

4.1 时间复杂度

Kruskal算法的时间复杂度主要由两部分组成:

  • 对边进行排序的时间复杂度:假设图中有 (E) 条边,使用比较排序算法(如快速排序、归并排序)对边按权值排序,时间复杂度为 (O(E \log E))。由于在连通图中 (E \geq V - 1),所以 (O(E \log E)) 可以近似为 (O(E \log V))。
  • 遍历边并判断连通性和合并连通分量的时间复杂度:在最坏情况下,需要遍历所有 (E) 条边,每次遍历需要使用并查集判断顶点的连通性和合并连通分量,其时间复杂度近似为 (O(\alpha(V)))((\alpha(V)) 是阿克曼函数的反函数,增长极其缓慢,在实际应用中可近似看作常数时间)。因此,遍历边的总时间复杂度为 (O(E \alpha(V))),可近似为 (O(E))。

综合以上两部分,Kruskal算法的时间复杂度为 (O(E \log V)),其中 (E) 是边的数量,(V) 是顶点的数量。

4.2 空间复杂度

Kruskal算法的空间复杂度主要取决于存储图的边集合和并查集数据结构所需的空间:

  • 存储边集合:需要存储图中的所有边,边的数量为 (E),因此存储边集合的空间复杂度为 (O(E))。
  • 并查集数据结构:需要存储每个顶点的父节点信息和秩信息,顶点数量为 (V),所以并查集的空间复杂度为 (O(V))。

综合起来,Kruskal算法的空间复杂度为 (O(E + V)),在实际应用中,由于 (E \geq V - 1),所以空间复杂度可近似为 (O(E))。

五、Kruskal算法应用场景

5.1 网络布线

在构建计算机网络、电力网络等实际场景中,需要在多个节点之间进行连接,同时要使连接的成本最小。将各个节点看作图的顶点,节点之间的连接看作边,边的权值可以是连接的成本(如铺设电缆的长度、费用等),通过Kruskal算法可以找到成本最小的网络连接方案,确保所有节点都能连通且总费用最低。

5.2 聚类分析

在数据聚类问题中,可以将数据点看作图的顶点,顶点之间的距离(如欧几里得距离)看作边的权值。通过Kruskal算法构建最小生成树,然后从最小生成树中删除权值较大的边,将图分割成多个连通分量,每个连通分量就对应一个聚类。这种方法可以根据不同的需求,通过调整删除边的策略,得到不同数量和规模的聚类结果 。

5.3 电路设计

在集成电路设计中,需要将多个电子元件连接起来,同时要尽量减少连线的长度,以降低电路的成本和功耗。将电子元件看作顶点,元件之间的连接看作边,边的权值为连线长度,利用Kruskal算法可以找到连接所有元件的最短连线方案,优化电路设计。

总结

Kruskal算法以其简洁高效的贪心策略,成为求解最小生成树问题的经典算法。通过对算法原理、执行流程的详细讲解,以及Python、C++、Java三种编程语言的代码实现,我们深入理解了Kruskal算法的具体实现方式,事不宜迟,我们不妨找些算法题刷起来吧!

That’s all, thanks for reading!
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

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

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

相关文章

微信小程序返回上一页监听

本文实现的是微信小程序在返回上一页时获取通知并自定义业务。 最简单的实现&#xff1a; 使用 wx.enableAlertBeforeUnload() 优点&#xff1a;快速接入 缺点&#xff1a;手势不能识别、无法自定义弹窗内容&#xff08;仅询问&#xff09; 方法二&#xff1a; page-conta…

Excel 统计某个字符串在指定区域出现的次数

【本文概要】 Excel 统计某个字符串在指定区域出现的次数&#xff1a; 1、Excel 统计一个单元格内的某字符串的出现次数 2、Excel 统计某一列所有单元格内的某字符串的出现次数 3、Excel 统计某一区域所有单元格内的某字符串的出现次数 1、Excel 统计一个单元格内的某字符串的出…

生物化学:药品药物 营养和补充剂信息 第三方认证信息 常见误区 汇总

常见维生素和矿物质成分表 成分名称好处副作用&#xff08;超量或敏感情况&#xff09;运作方式推荐日剂量&#xff08;成人&#xff09;剂量说明维生素A&#xff08;视黄醇&#xff09;视力、免疫、皮肤健康过量可致肝损伤、头痛、脱发调节视网膜功能、细胞分化700–900 g RA…

mock库知识笔记(持续更新)

文章目录 mock简介导入方式参数简介使用场景&#xff08;待更新&#xff09;常见问题总结&#xff08;待更新&#xff09;Python代码官网 mock简介 mock是一个模拟对象库&#xff0c;具有模拟其他python对象的功能&#xff0c;还能指定模拟对象的返回值和设置模拟对象的属性。…

扇形 圆形 面积公式

✅ 一、圆的面积公式 全圆面积&#xff1a; A circle π r 2 A_{\text{circle}} \pi r^2 Acircle​πr2 ✅ 二、扇形的面积公式&#xff08;两种制式&#xff09; 弧度制&#xff1a; A sector 1 2 r 2 θ A_{\text{sector}} \frac{1}{2} r^2 \theta Asector​21​r2θ …

怎样将win11+ubuntu双系统的ubuntu从机械硬盘迁移至固态硬盘(1)

将 Ubuntu 从机械硬盘迁移到固态硬盘是一个涉及多个步骤的过程。以下是一个基本的迁移指南&#xff1a; 1. 前期准备 1.1 备份数据&#xff1a; 确保你已备份数据&#xff0c;以防止在迁移过程中出现意外导致任何数据丢失。 1.2 固态硬盘安装&#xff1a; 确保固态硬盘正确…

js中common.js和ECMAScript.js区别

以下是关于 CommonJS 和 ECMAScript Modules&#xff08;ESM&#xff09;的详细对比分析&#xff0c;包含底层原理和示例说明&#xff1a; &#x1f9e9; 核心差异对比表 特性CommonJSES Modules来源Node.js 社区规范ECMAScript 语言标准加载方式动态加载&#xff08;运行时解…

玻纤效应的时序偏差

随着比特率继续飙升&#xff0c;光纤编织效应时序偏移正成为一个越来越严重的问题。对于 5GB/s 及以上的信号传输速率&#xff0c;它实际上会毁了您的一天。例如&#xff0c;左图显示由于 12.7 英寸的纤维编织效果&#xff0c;5GB/s 的接收眼完全闭合。使用 Agilent ADS 软件进…

异步上传石墨文件进度条前端展示记录(采用Redis中String数据结构实现)

事件起因是客户现场需要从石墨文档中获取文件信息&#xff0c;文件信息存在存在多个&#xff0c;进行批量上传。为了用户的友好型体验&#xff0c;需要做进行条展示的方式&#xff0c;具体实现见下文… 上传流程介绍 石墨文档支持从链接&#x1f517;方式获取文件信息&#xf…

3D建模的全景图谱:从55个工具到元宇宙的数字革命

3D建模已从专业工程师的工具箱演变为全民创作的数字语言。从代码驱动的精确建模到AI自动生成纹理&#xff0c;从开源协作到程序化生成城市&#xff0c;技术正重塑我们创造虚拟世界的方式。本文将系统解析55个核心3D建模工具/插件&#xff0c;涵盖在线编辑器、开源软件、程序化生…

jsrpc进阶模式 秒杀js前端逆向问题 burp联动进行爆破

案例演示 思路就是 这个 jsrpc远程加载加密函数的方法就是 在js代码中进行插入一个 远程加载的代码 从而实现 &#xff1a; 第一步还是使用 js_tools 进行 查找算法的位置 这个可以帮助我们找到明文>密文 加密算法函数的位置 因为这个需要我们进行js前端代码的修改 所以…

基于BERT-Prompt的领域句子向量训练方法

基于BERT-Prompt的领域句子向量训练方法 一、核心原理:基于BERT-Prompt的领域句子向量训练方法 论文提出一种结合提示学习(Prompt Learning)和BERT的领域句子向量训练方法,旨在解决装备保障领域文本的语义表示问题。核心原理如下: 以下通过具体例子解释传统词向量方法和…

Python PyMySQL

1.PyMySQL是什么 是Python操作mysql的一个包 2.PyMySQL使用基本步骤 2.1 创建连接 conn pymysql.connect(host10.248.53.148,password123456,port3306,userroot,databasetest_database,charsetutf8)2.2 游标 2.2.1 什么是游标 游标实际上是一种能从包括多条数据记录的结果…

OC—UI学习-1

OC—UI学习 UILabel UILabel是UIKit框架中的一个类Label主要参数 text&#xff1a;文本frame&#xff1a;位置框架backgroundcolor&#xff1a;背景颜色textAlignment&#xff1a;设置文本在Label中的位置textColor&#xff1a;文本颜色shadowColor&#xff1a;阴影颜色shado…

【应用密码学】实验七 Hash函数——SM3

一、实验要求与目的 理解哈希函数的基本原理及在密码学中的应用&#xff1b;掌握国密哈希标准 SM3 的算法结构&#xff1b;编程实现 SM3 摘要算法&#xff0c;包括消息填充、消息扩展、压缩函数及摘要输出&#xff1b;对中间变量 W、W′ 和 A~H 的迭代过程进行可视化&#xff…

进行性核上性麻痹护理之道:助力患者舒适生活

进行性核上性麻痹是一种缓慢进展的神经退行性疾病&#xff0c;主要影响患者的运动、语言和吞咽功能&#xff0c;给日常生活带来诸多不便。除了遵医嘱接受药物或物理治疗&#xff0c;科学的健康护理对延缓病情发展、提升生活质量尤为重要。 运动康复是护理的关键环节。由于患者常…

5G 核心网中 NRF 网元的功能、接口及参数详解

引言 在 5G 核心网的架构体系里,网络存储功能(Network Repository Function,NRF)占据着关键地位,承担着核心网网络功能(Network Function,NF)的注册、发现以及服务管理等重要任务,为整个 5G 网络的高效稳定运行提供了坚实支撑。接下来,让我们深入剖析 NRF 网元在注册…

HUAWEI交换机配置镜像口验证(eNSP)

技术术语&#xff1a; 流量观察口&#xff1a;就是我们常说的镜像口&#xff0c;被观察的流量的引流目的端口 流量源端口&#xff1a;企业生产端口&#xff0c;作为观察口观察对象。 命令介绍&#xff1a; [核心交换机]observe-port [观察端口ID或编号&#xff08;数字&am…

Spring AI Alibaba 发布企业级 MCP 分布式部署方案

作者&#xff1a; 影子&#xff0c;刘宏宇&#xff0c;刘军 Spring AI 通过集成 MCP 官方的 java sdk&#xff0c;让 Spring Boot 开发者可以非常方便的开发自己的 MCP 服务&#xff0c;把自己企业内部的业务系统通过标准 MCP 形式发布为 AI Agent 能够接入的工具&#xff1b;…

Redis实战-缓存篇(万字总结)

前言&#xff1a; 今天结合黑马点评这个项目&#xff0c;讲下有关Redis缓存的一些内容&#xff0c;例如缓存更新策略&#xff0c;缓存穿透&#xff0c;雪崩和击穿等。 今日所学&#xff1a; 什么是缓存缓存更新策略缓存穿透缓存雪崩缓存击穿缓存工具封存 目录 1.什么是缓存…