01数据结构-Prim算法

  • 1.普利姆(Prim)算法
    • 1.1Prim算法定义
    • 1.2Prim算法逻辑
    • 1.3Prim代码分析
  • 2.Prim算法代码实现

1.普利姆(Prim)算法

1.1Prim算法定义

Prim算法在找最小生成树的时候,将顶点分为两类,一类是在查找的过程中已经包含在生成树中的顶点(假设为A类),剩下的另一类为B类。

对于给定的连通图,起始状态全部顶点都为B类。在找最小生成树时,选定任意一个顶点作为起始点,并将之从B类移至A类;然后找出B类中到A类中的顶点之间权值最小的顶点,将之从B类移至A类,如此重复,直到B类中没有顶点为止。所走过的顶点和边就是该连通图的最小生成树。(简单对比一下:上一节课的Kruskal是找边,这几棵的Prim算法是找顶点。)

1.2Prim算法逻辑

动态维护一个所有待激活的顶点数组,从图中任意选取一个顶点激活它,激活了后就能发现新的边,找到权值数组里最小的边,激活另外一个顶点,更新权值数组,再来找权值数组里最小的边,激活另外的顶点,以此重复,直到所有顶点都激活。

如图1我们先激活A,激活后发现新的边,更新权值数组里的值,A连着B,F,G,所以很明显我们需要更新B,F,G下的权值,其他没有直接连接的我们暂时认为权值无穷大,找到权值数组里面最小的边是AB这条,激活B顶点,发现新的边,更新权值数组里的边,B连着A,F,C,由于A和B已激活,我们看需不需要更新F和C下的权值,发现B到F的这条边的权值是7<16,我们更新F下的权值为7,C下的权值为10,激活F顶点。
图1
图1

以此重复这个过程,最终结果如图2,在找到C之后,就只有G没有激活了,G的有三条边,选取最小的那条边8作为权值数组里面的值。

图2图2

如图3,注意在这里Kruskal算法和Prim算法最后构成的最小生成树一样,但在实际过程中,这两种算法构建出来的最小生成树可能不一样。
图3
图3

1.3Prim代码分析

如图4我们定义一个cost数组,里面存的是权值数组,初始时,每个边都是INF(无穷大),一个visited数组存已经被激活的点,由于我不想只打印出最后的权值大小,我还想打印打出对应选取了哪些边在最小生成树中,初始化时全部设为-1,意味着没有激活前,没有其他的顶点到自己。

第一次激活A点,A连着有三条边,分别对应的顶点是B,F,G,在cost中A激活那一行填写对应的权值,在该行中找到最小的权值,标黄,cost标了黄色的格子意味着是当前一行权值数组中最小的边,激活它,后面在更新权值数组的时候就可以不用管黄色的那一列了。在visited数组中,由于A的激活,我们发现三条边对应的是B,F,G,说明A能到B,F,G我们就在A激活那一行中填写A到了这三个顶点,但我们一次只能确定一个谁可以被保留,因为我们在costA激活的那一行中,一次只能找到一个最小权值,有的同学可能会问,为什么要把visited中A激活那一行F和G下的A也暂时保留呢,由上1.2分析得后面B是要连F得,但如果连接BF的那条边很大,比连接AF下的边大,那么我们就不是B到F,而是A到F,如果不保留的话就找不到了。由于我们激活的是B,又开始对B执行上述逻辑操作,一直到所有顶点被激活。

visited中A激活那里B列下的A要标黄,意味着确定了最小生成树中其中某一条边的顶点是这两个

图4
图4

过程和结果如图5:
图5
图5

注意实际代码实现的时候并不是二维数组,而是意味数组,我这里画成二维数组只是方便看过程。

2.Prim算法代码实现

先来看测试的接口:

void setupMGraph(MatrixGraph *graph, int edgeValue) {//				  0    1    2    3    4    5   6char *names[] = {"A", "B", "C", "D", "E", "F", "G"};initMatrixGraph(graph, names, sizeof(names)/sizeof(names[0]), 0, edgeValue);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);
}void test02() {MatrixGraph graph;EdgeSet *result;int sumWeight = 0;setupMGraph(&graph, INF);result = malloc(sizeof(EdgeSet) * (graph.edgeNum - 1));if (result == NULL) {return;}sumWeight = PrimMGraph(&graph, 0, result);printf("sumWeight = %d\n", sumWeight);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);}
}

我们在void setupMGraph(MatrixGraph *graph, int edgeValue)中没有相连的两个顶点之间的权值赋为INF,相连的两个顶点间赋值相应的权值,在void test02()中用PrimMGraph(&graph, 0, result);得到权值总和并随后打印出边和顶点的信息

Prim算法核心:int PrimMGraph(const MatrixGraph* graph, int startV, EdgeSet* result);

/* graph: 表示指向邻接矩阵的图结构* startV:表示激活的第一个顶点坐标* result:表示最小生成树的边的激活情况*/
int PrimMGraph(const MatrixGraph* graph, int startV, EdgeSet* result) {int *cost = malloc(sizeof(int) * graph->nodeNum);			// 图中各顶点的权值数组int *mark = malloc(sizeof(int) * graph->nodeNum);			// 图中顶点激活的状态int *visited = malloc(sizeof(int) * graph->nodeNum);		// 从哪个顶点开始访问,-1表示没有被访问到int sum = 0;// 1. 更新第一个节点激活的状态for (int i = 0; i < graph->nodeNum; i++) {cost[i] = graph->edges[startV][i];mark[i] = 0;// 1.1 更新visit信息,从哪个节点可以访问if (cost[i] < INF) {visited[i] = startV;} else {visited[i] = -1;}}mark[startV] = 1;int k = 0;// 2. 动态激活节点,找最小值for (int i = 0; i < graph->nodeNum - 1; ++i) {// 找未加入树且cost最小的顶点kint min = INF;k = 0;for (int j = 0; j < graph->nodeNum; ++j) {if (mark[j] == 0 && cost[j] < min) {min = cost[j];k = j;}}// 将k加入树,记录边信息mark[k] = 1;result[i].begin = visited[k];result[i].end = k;result[i].weight = min;sum += min;// 每激活一个顶点,需要更新cost数组for (int j = 0; j < graph->nodeNum; ++j) {if (mark[j] == 0 && graph->edges[k][j] < cost[j]) {cost[j] = graph->edges[k][j];visited[j] = k;}}}free(cost);free(mark);free(visited);return sum;
}

我们需要申请三个空间分别用来存图中各顶点的权值数组,图中顶点激活的状态和对应带权边的两个顶点信息。

先来看1.更新第一个节点激活的状态,在cost里面存放激活的第一个顶点与图中其他顶点间的边的权值大小,并默认所有顶点还未被访问,然后更新visited数组中的信息,如果cost里面存的权值大小小于INF,则把对应visited数组下的值填成starV,说明两个顶点之间有边,否则就认为starV顶点与其无边,在对应visited数组下设为-1,初始化完我们认为starV已被访问:mark[startV] = 1;此时三个数组如图6:
图6
图6

定义一个k标记待加入的顶点:
在每次循环中,程序会遍历所有未加入生成树的顶点(mark[j] == 0),找到 cost[j] 最小的顶点,并用 k 记录该顶点的索引。这个顶点 k 就是本次要加入生成树的顶点。
作为新顶点更新后续状态:
当 k 被标记为 “已加入生成树”(mark[k] = 1)后,程序会以 k 为新的起点,更新其他未加入顶点的 cost 和 visited 数组。此时 k 成为 “已生成树” 的一部分,用于后续计算其他顶点与树的最小连接边权。

来看2,先定义一个最小值,这个最小值存的是INF,这样比这个最小值小就可以加入cost里了,找未加入树且cost最小的顶点k,找到过后将k加入树,记录边信息并把k加入mark标记中说明已被访问。每次激活一个顶点,需要更新cost里面的权值当然更新的时候只更新没有访问过的顶点,因为已被访问过的顶点已经被我们纳入了最小生成树中,即我们不需要更新图4,图5中cost和visited里面标黄的部分。循环 graph->nodeNum - 1次,因为先前已经加入了starV顶点,初始时有 1 个顶点,循环 graph->nodeNum - 1 次后,新增 graph->nodeNum - 1 个顶点,总共加入的顶点数为graph->nodeNum个。

最后测一下:

#include <stdio.h>
#include <stdlib.h>
#include "Kruskal.h"
#include "Prim.h"void setupMGraph(MatrixGraph *graph, int edgeValue) {//				  0    1    2    3    4    5   6char *names[] = {"A", "B", "C", "D", "E", "F", "G"};initMatrixGraph(graph, names, sizeof(names)/sizeof(names[0]), 0, edgeValue);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);
}void test02() {MatrixGraph graph;EdgeSet *result;int sumWeight = 0;setupMGraph(&graph, INF);result = malloc(sizeof(EdgeSet) * (graph.edgeNum - 1));if (result == NULL) {return;}sumWeight = PrimMGraph(&graph, 0, result);printf("sumWeight = %d\n", sumWeight);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);}
}int main() {test02();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\03_GraphStruct\MiniTree.exe
sumWeight = 36
edge 1: <A> ---- <12> ---- <B>
edge 2: <B> ---- <7> ---- <F>
edge 3: <F> ---- <2> ---- <E>
edge 4: <E> ---- <4> ---- <D>
edge 5: <D> ---- <3> ---- <C>
edge 6: <E> ---- <8> ---- <G>进程已结束,退出代码为 0

大概先写这些吧,今天的博客就先写到这,谢谢您的观看。

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

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

相关文章

CacheBlend:结合缓存知识融合的快速RAG大语言模型推理服务

温馨提示&#xff1a; 本篇文章已同步至"AI专题精讲" CacheBlend&#xff1a;结合缓存知识融合的快速RAG大语言模型推理服务 摘要 大语言模型&#xff08;LLMs&#xff09;通常在输入中包含多个文本片段&#xff0c;以提供必要的上下文。为了加速对较长LLM输入的预…

Docker 在 Linux 中的额外资源占用分析

Docker 本身作为一个运行时环境&#xff0c;除了容器应用本身消耗的资源外&#xff0c;还会引入一些额外的开销。主要体现在以下几个方面&#xff1a; 1. 存储空间占用 (Disk Space) 这是最显著的额外开销&#xff0c;主要来源于 Docker 的存储驱动&#xff08;如 overlay2&…

[激光原理与应用-264]:理论 - 几何光学 - 什么是焦距,长焦与短焦的比较

长焦与短焦透镜是光学系统中两类核心组件&#xff0c;其成像特性在焦距、视角、景深、像场特性及典型应用中存在显著差异。以下从多个维度进行详细对比&#xff1a;一、核心参数对比参数长焦透镜短焦透镜焦距范围通常 >50mm&#xff08;全画幅相机标准&#xff09;通常 <…

el-input 复制大量数据导致页面卡顿问题解决

问题根源 复制粘贴操作会瞬间触发大量 input 事件&#xff0c;导致 Vue 频繁更新响应式数据&#xff0c;引发性能瓶颈。 解决方案&#xff1a;使用 .lazy 修饰符 <el-input v-model.lazy"inputValue" />

PCIe Electrical Idle Sequences ( EIOS and EIEOS )

前言 PCI Express (PCIe)协议中&#xff0c;EIOS (Electrical Idle Ordered Set) 和 EIEOS (Electrical Idle Exit Ordered Set) 是在高速链路管理和状态切换过程中极为重要的特殊序列。下面做详细解释&#xff1a; 一、EIOS&#xff08;Electrical Idle Ordered Set&#xff0…

【GPT入门】第45课 无梯子,linux/win下载huggingface模型方法

【GPT入门】第45课 无梯子&#xff0c;下载huggingface模型方法1.下载模型代码2. linux 设置镜像与加速3.windows1.下载模型代码 from transformers import AutoModelForCausalLM, BertTokenizer, BertForSequenceClassificationmodel_dir /root/autodl-tmp/model_hf# 加载模…

计算机网络摘星题库800题笔记 第5章 传输层

第5章 传输层5.1 传输层概述题组闯关1.Internet 传输层滑动窗口协议规定 ( )。 A. 网络接收分组的最低效率&#xff0c;只需要重传未被确认的分组 B. 固定的窗口大小&#xff0c;只需要重传未被确认的分组 C. 网络接收分组的最低效率&#xff0c;固定的窗口大小 D. 未被确认的分…

Apache虚拟主机三种配置实战

一、虚拟主机概述 目的&#xff1a;实现单台服务器部署多个独立站点 三种部署方式&#xff1a; 相同IP 不同端口不同IP 相同端口相同IP和端口 不同域名&#xff08;FQDN&#xff09; 示例目标&#xff1a;在服务器上部署 baidu 和 taobao 两个站点方式1&#xff1a;相同IP …

【SpringBoot】04 基础入门 - 自动配置原理入门:依赖管理 + 自动配置

文章目录前言一、Spring Boot Maven项目POM文件解析1. 基础项目信息2. 父项目继承3. 依赖管理4. 构建配置5. 属性配置Spring Boot特性体现典型Spring Boot项目特点二、依赖管理1、父项目做依赖管理无需关注版本号&#xff0c;自动版本仲裁修改自动仲裁的版本官网文档2、依赖项引…

机器学习—— TF-IDF文本特征提取评估权重 + Jieba 库进行分词(以《红楼梦》为例)

使用 Jieba 库进行 TF-IDF 关键词提取&#xff08;以《红楼梦》为例&#xff09;在中文文本分析中&#xff0c;TF-IDF&#xff08;Term Frequency - Inverse Document Frequency&#xff09; 是最常用的关键词提取方法之一。它通过评估词在单个文档中的出现频率和在所有文档中的…

Kotlin语法整理

Kotlin语法整理 Kotlin语法整理 一、基本数据类型 共8种 二、变量的声明三、条件 1. if…else if…else语句2. when 语句 四、循环 1. while 语句2. do…while 语句3. for 语句4. repeat 语句5. break 语句6. continue 语句 五、数组 1. 创建元素未初始化的数组2. 创建元素初始…

跨平台低延迟的RTMP推流播放在无纸化会议与智慧教室的技术设计和架构实践

✳️ 引言&#xff1a;让每一块屏幕“同频”的核心技术 无纸化会议与智慧教室&#xff0c;正在从“辅助工具”走向“核心基础设施”&#xff0c;成为政企数字化与教育信息化建设的标配。它们的核心诉求并不只是替代纸质文档或黑板&#xff0c;而是要在多终端、多地点、多网络环…

最优扩展大型语言模型测试时计算量可能比扩展模型参数更有效

摘要 通过增加测试时计算量使大型语言模型&#xff08;LLMs&#xff09;提升输出效果&#xff0c;是构建能基于开放自然语言自主改进的通用智能体的重要步骤。本文研究LLMs推理阶段计算量的扩展规律&#xff0c;重点回答以下问题&#xff1a;若允许LLM使用固定但可观的推理阶段…

GPT5评测对比与使用

经过长达一年的技术迭代&#xff0c;OpenAI正式推出GPT-5系列模型&#xff0c;包含GPT-5&#xff08;标准版&#xff09;、GPT-5-mini&#xff08;轻量版&#xff09;和GPT-5-nano&#xff08;极简版&#xff09;三个版本&#xff0c;定价策略保持统一。本次升级在性能、效率与…

Git与CI/CD相关知识点总结

Git与CI/CD相关知识点总结 1. Git对象模型与存储机制 1.1 Git对象类型 Commit对象&#xff1a;包含提交信息、作者、时间、父commit引用、树对象引用Tree对象&#xff1a;描述目录结构和文件引用Blob对象&#xff1a;实际的文件内容 1.2 存储机制特点 增量存储&#xff1a;每次…

CS2服务器是何方神圣

CS2服务器是何方神圣CS2「子刷新频率」深度拆解&#xff1a;从官方宣言到“吞子弹”真相00 先给结论01 官方原话到底说了什么02 一条时间线看懂「Sub-tick」03 技术解剖&#xff1a;Sub-tick 的实现细节3.1 输入包结构&#xff08;Valve 公开源码节选&#xff09;3.2 连续积分&…

Docker守护进程安全加固在香港VPS环境的操作标准

Docker守护进程安全加固在香港vps环境的操作标准随着云计算技术的普及&#xff0c;Docker守护进程安全加固已成为香港VPS环境中不可忽视的重要环节。本文将系统性地介绍如何通过配置优化、访问控制、网络隔离等维度&#xff0c;在香港虚拟私有服务器上建立符合企业级安全标准的…

Rust 项目编译故障排查:从 ‘onnxruntime‘ 链接失败到 ‘#![feature]‘ 工具链不兼容错误

Rust 项目编译故障排查报告&#xff1a;从原生库链接失败到工具链不兼容 场景: 编译一个本地 Rust 项目时遇到连续的编译错误。一、 故障现象概述 在对一个 Rust 项目执行 cargo build 命令时&#xff0c;先后遇到了两个不同性质的编译错误&#xff0c;导致编译流程中断。初始错…

K8s 1.32.6版本部署文档

主机配置 作用IP地址操作系统配置关键组件k8s-master172.16.1.30Rocky Linux release 94C/4G/50GBkube-apiserver, etcd,dockerk8s-node1172.16.1.31Rocky Linux release94C/4G/50GBkubelet, kube-proxy,dockerk8s-node2172.16.1.32Rocky Linux release 94C/4G/50GBkubelet, k…

第十六届蓝桥杯大赛青少组 C++ 省赛真题解析(2025年8月10日)

第一题 题目:运行以下程序,输出的结果是()。 #include<bits/stdc++.h> using namespace std; int func(int y) { y -= 5; cout << "x"; return 0; } int main() { int x = 10, y = 5; if (x > y || func(y)) cout &…