【图的应用一:最小生成树】- 用 C 语言实现普里姆算法和克鲁斯卡尔算法

目录

一、最小生成树 

二、普里姆算法的构造过程

三、普里姆算法的实现

四、克鲁斯卡尔算法的构造过程

五、克鲁斯卡尔算法的实现



一、最小生成树 

假设要在 n 个城市之间建立通信联络网,则连通 n 个城市只需要 n - 1 条线路。这时,自然会考虑这样一个问题,如何在最节省经费的前提下建立这个通信网。

在每两个城市之间都可设置一条线路,相应地都要付出一定的经济代价。n 个城市之间,最多可能设置 ​ C_n^2 = \dfrac{n(n - 1)}{2} 条线路,那么如何在这些可能的线路中选择 n - 1 条,以使总的耗费最少呢

可以用连通网来表示 n 个城市,以及 n 个城市间可能设置的通信路线,其中网的顶点表示城市,边表示两城市之间的线路,赋予边的权值表示相应的代价。对于 n 个顶点的连通网可以建立许多不同的生成树,每一颗生成树都可以是一个通信网,最合理的通信网应该是各边代价之和最小的那颗生成树,称为连通网的最小代价生成树(Minimum Cost Spanning Tree),简称最小生成树

构造最小生成树有多种算法,其中多数算法利用了最小生成树的下列一种简称为 MST 的性质

假设 N = (V, E) 是一个连通网,U 是顶点集 V 的一个非空真子集。若 (u, v) 是 N 中所有的一个顶点在 U(u \in U​)中,另一个顶点在 V - U(​v \in V - U)中的边里,具有最小权值的一条边,则必存在一棵包含边 (u, v) 的最小生成树。

可以用反证法来证明

假设网 N 的任何一棵最小生成树都不包含边 (u, v),T 是连通网上的一棵最小生成树,由生成树的定义可知,T 中必存在一条从 u 到 v 的路径 P,且在 P 上必存在一条边 (u', v'),其中 u' \in U, v' \in V - U​。

当将边 (u, v) 加入到 T 中时,(u, v) 和 P 构成了一条回路,删去 (u', v') 便可消除回路,同时得到另一颗生成树 T‘。因为 (u, v) 的权值不高于 (u', v'),所以 T' 的权值亦不高于 T,那么 T’ 是包含 (u, v) 的一棵最小生成树,和假设矛盾。

普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法是两个利用 MST 性质构造最小生成树的算法。


二、普里姆算法的构造过程

假设 N = (V, E) 是连通网,TE 是 N 上最小生成树中边的集合。

  1. U = \{u_0\}(u_0 \in V), TE = \{\}​。

  2. 在所有 ​u \in U, v \in V - U 的边 (u, v) \in E 中找一条权值最小的边 (u_0, v_0) 并入集合 TE,同时 ​v_0 并入 U。

  3. 重复 2,直至 U = V 为止。

此时 TE 中必有 n - 1 条边,则 T = (V, TE) 为 N 的最小生成树。

下图所示为一个连通网 G5 从 v1 开始构造最小生成树的例子。可以看出,普里姆算法逐步增加 U 中的顶点,可称为 "加点法"

每次选择最小边时,可能存在多条同样权值的边可选,此时任选其一即可


三、普里姆算法的实现

假设无向网 N邻接矩阵形式存储,从顶点 u 出发构造 N 的最小生成树 T,要求输出 T 的各条边。

为实现这个算法附设了两个辅助数组 lowcostArr 和 adjVexPosArr。对每个顶点 ,lowcostArr[i - 1] = Min{ cost​ },即表示 N 中所有的一个顶点在 U 中,另一个顶点是 vi 的边里,最小边上的权值;adjVexPosArr[i - 1] 则表示那条最小边在 U 中的那个顶点在图中的位置

AMGraph.h

#pragma once
​
// 无向网
typedef char VertexType;
typedef int EdgeType;
​
#define DEFAULT_CAPACITY 2
​
typedef struct AMGraph
{VertexType* vertices;EdgeType** edges;int vSize;int eSize;int capacity;
}AMGraph;
​
// 基本操作
void AMGraphInit(AMGraph* pg);
​
void ShowAdjMatrix(AMGraph* pg);
​
int GetVertexPos(AMGraph* pg, VertexType v);
​
void InsertVertex(AMGraph* pg, VertexType v);
void InsertEdge(AMGraph* pg, VertexType v1, VertexType v2, EdgeType cost);
​
// 普里姆算法
void MiniSpanTree_Prim(AMGraph* pg, VertexType u);

AMGraph.c

#include "AMGraph.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
​
void AMGraphInit(AMGraph* pg)
{assert(pg);pg->vSize = pg->eSize = 0;pg->capacity = DEFAULT_CAPACITY;pg->vertices = (VertexType*)malloc(sizeof(VertexType) * pg->capacity);assert(pg->vertices);
​pg->edges = (EdgeType**)malloc(sizeof(EdgeType*) * pg->capacity);assert(pg->edges);for (int i = 0; i < pg->capacity; ++i){pg->edges[i] = (EdgeType*)malloc(sizeof(EdgeType) * pg->capacity);assert(pg->edges[i]);for (int j = 0; j < pg->capacity; ++j){if (i == j)pg->edges[i][j] = 0;elsepg->edges[i][j] = INT_MAX;}}
}
​
void ShowAdjMatrix(AMGraph* pg)
{assert(pg);printf("  ");for (int i = 0; i < pg->vSize; ++i){printf("%c ", pg->vertices[i]);}printf("\n");
​for (int i = 0; i < pg->vSize; ++i){printf("%c ", pg->vertices[i]);for (int j = 0; j < pg->vSize; ++j){if (pg->edges[i][j] == INT_MAX)printf("# ");  // 用 # 代替 ∞elseprintf("%d ", pg->edges[i][j]);}printf("\n");}
}
​
int GetVertexPos(AMGraph* pg, VertexType v)
{assert(pg);for (int i = 0; i < pg->vSize; ++i){if (pg->vertices[i] == v)return i;}return -1;
}
​
void InsertVertex(AMGraph* pg, VertexType v)
{assert(pg);// 考虑是否需要扩容if (pg->vSize == pg->capacity){VertexType* tmp1 = (VertexType*)realloc(pg->vertices, sizeof(VertexType) * 2 * pg->capacity);assert(tmp1);pg->vertices = tmp1;
​EdgeType** tmp2 = (EdgeType**)realloc(pg->edges, sizeof(EdgeType*) * 2 * pg->capacity);assert(tmp2);pg->edges = tmp2;for (int i = 0; i < pg->capacity; ++i){EdgeType* tmp3 = (EdgeType*)realloc(pg->edges[i], sizeof(EdgeType) * 2 * pg->capacity);assert(tmp3);pg->edges[i] = tmp3;for (int j = pg->capacity; j < 2 * pg->capacity; ++j){pg->edges[i][j] = INT_MAX;  }}for (int i = pg->capacity; i < 2 * pg->capacity; ++i){pg->edges[i] = (EdgeType*)malloc(sizeof(EdgeType) * 2 * pg->capacity);assert(pg->edges[i]);for (int j = 0; j < 2 * pg->capacity; ++j){if (i == j)pg->edges[i][j] = 0;elsepg->edges[i][j] = INT_MAX;}}
​pg->capacity *= 2;}// 插入顶点pg->vertices[pg->vSize++] = v;
}
​
void InsertEdge(AMGraph* pg, VertexType v1, VertexType v2, EdgeType cost)
{assert(pg);int pos1 = GetVertexPos(pg, v1);int pos2 = GetVertexPos(pg, v2);if (pos1 == -1 || pos2 == -1)return;
​if (pg->edges[pos1][pos2] != INT_MAX)return;
​pg->edges[pos1][pos2] = pg->edges[pos2][pos1] = cost;++pg->eSize;
}
​
// 普里姆算法的实现
void MiniSpanTree_Prim(AMGraph* pg, VertexType u)
{assert(pg);EdgeType* lowcostArr = (EdgeType*)malloc(sizeof(EdgeType) * pg->vSize);int* adjVexPosArr = (int*)malloc(sizeof(int) * pg->vSize);assert(lowcostArr && adjVexPosArr);
​int pos = GetVertexPos(pg, u);if (pos == -1)return;for (int i = 0; i < pg->vSize; ++i){if (i != pos){lowcostArr[i] = pg->edges[pos][i];adjVexPosArr[i] = pos;}else{lowcostArr[i] = 0;  // 初始,U = {u}}}// 选择其余 pg->vSize - 1 个顶点,生成 pg->vSize - 1 条边for (int i = 0; i < pg->vSize - 1; ++i){EdgeType min = INT_MAX;int minIndex = -1;for (int j = 0; j < pg->vSize; ++j){if (lowcostArr[j] != 0 && lowcostArr[j] < min){min = lowcostArr[j];minIndex = j;}}printf("(%c, %c)\n", pg->vertices[adjVexPosArr[minIndex]], pg->vertices[minIndex]);lowcostArr[minIndex] = 0;  // 将顶点并入 U 
​for (int j = 0; j < pg->vSize; ++j){if (pg->edges[minIndex][j] < lowcostArr[j]){lowcostArr[j] = pg->edges[minIndex][j];adjVexPosArr[j] = minIndex;}}}free(lowcostArr);free(adjVexPosArr);
}

Test.c

#include "AMGraph.h"
#include <stdio.h>
​
int main()
{AMGraph g;AMGraphInit(&g);InsertVertex(&g, 'A');InsertVertex(&g, 'B');InsertVertex(&g, 'C');InsertVertex(&g, 'D');InsertVertex(&g, 'E');InsertVertex(&g, 'F');InsertEdge(&g, 'A', 'B', 6);InsertEdge(&g, 'A', 'C', 1);InsertEdge(&g, 'A', 'D', 5);InsertEdge(&g, 'B', 'C', 5);InsertEdge(&g, 'B', 'E', 3);InsertEdge(&g, 'C', 'D', 5);InsertEdge(&g, 'C', 'E', 6);InsertEdge(&g, 'C', 'F', 4);InsertEdge(&g, 'D', 'F', 2);InsertEdge(&g, 'E', 'F', 6);ShowAdjMatrix(&g);printf("\n");
​MiniSpanTree_Prim(&g, 'A');return 0;
}


四、克鲁斯卡尔算法的构造过程

假设连通网 N = (V, E)。

  1. 令最小生成树的初始状态为只有 n 个顶点而无边的非连通图 T = (V, {}),图中每个顶点自成一个连通分量。

  2. 在 E 中选择权值(代价)最小的边,若该边依附的顶点落在不同的连通分量上(即不形成回路),则将此边加入到 T 中,否则舍去此边而选择下一条权值最小的边。

  3. 重复 2,直至 T 中所有顶点都在一个连通分量上为止。

例如,下图所示为依照克鲁斯卡尔算法对连通网 G5 构造一棵最小生成树的过程。权值 1、2、3、4 的 4 条边由于满足上述条件,则先后被加入到 T 中;权值为 5 的两条边 (v1, v4) 和 (v3, v4) 被舍去,因为它们依附的两顶点在同一连通分量上,它们若加入 T 中,则会使 T 中产生回路,而下一条权值(= 5)最小的边 (v2, v3) 联结两个连通分量,则可加入 T。由此,构造出了一棵最小生成树。

可以看出,克鲁斯卡尔算法逐步增加生成树的边,与普里姆算法相比,可称为 "加边法"。与普里姆算法一样,每次选择最小边时,可能有多条同样权值的边可选,可以任选其一


五、克鲁斯卡尔算法的实现

假设无向网 G邻接矩阵形式存储,构造 G 的最小生成树,输出 T 的各条边。

typedef struct Edge
{int x;  // 边的起点在图中的位置int y;  // 边的终点在图中的位置EdgeType cost;  // 边上的权值(即代价)
}Edge;
​
int CmpByCost(const void* e1, const void* e2)
{return (*(Edge*)e1).cost - (*(Edge*)e2).cost;
}
​
void MiniSpanTree_Kruskal(AMGraph* pg)
{assert(pg);Edge* edges = (Edge*)malloc(sizeof(Edge) * pg->eSize);assert(edges);int k = 0;for (int i = 0; i < pg->vSize; ++i){for (int j = i + 1; j < pg->vSize; ++j){if (pg->edges[i][j] != INT_MAX){edges[k].x = i;edges[k].y = j;edges[k].cost = pg->edges[i][j];++k;}}}qsort(edges, pg->eSize, sizeof(Edge), CmpByCost);
​int* vexSet = (int*)malloc(sizeof(int) * pg->vSize);  // 标识各顶点所属的连通连通分量assert(vexSet);for (int i = 0; i < pg->vSize; ++i){vexSet[i] = i;}
​for (int i = 0; i < pg->eSize; ++i){int x = edges[i].x;int y = edges[i].y;int vs1 = vexSet[x];int vs2 = vexSet[y];if (vs1 != vs2){printf("(%c, %c)\n", pg->vertices[x], pg->vertices[y]);for (int j = 0; j < pg->vSize; ++j){if (vexSet[j] == vs2)  vexSet[j] = vs1;}}}
​free(edges);free(vexSet);
}

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

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

相关文章

Android的组件、布局学习

介绍 公司组织架构调整&#xff0c;项目组需要承接其他项目组的android项目&#xff0c;负责维护和开发新需求&#xff0c;故学习下基础语法和项目开发。 组件学习 Toolbarheader布局部分 就是app最顶部的部分 他的显示与否&#xff0c;是与F:\androidProject\android_lear…

功能点估算的常规流程

功能点估算流程在软件项目管理中起着重要的作用&#xff0c;它可以帮助项目团队更好地理解项目的需求和目标&#xff0c;从而提高项目的成功率和效率。如果功能点估算未按流程进行&#xff0c;可能会导致项目估算不准确&#xff0c;估算时间超出预期等问题。 因此功能点估算的常…

设计测试用例(万能思路 + 六种设计用例方法)(详细 + 图解 + 实例)

一、设计测试用例的万能思路 针对某个物品/功能进行测试。 万能思路&#xff1a;功能测设 界面测试 性能测试 兼容性测试 易用性测试 安全测试。 总结&#xff1a; 功能测试&#xff1a; 水杯&#xff1a;装水、喝水... 注册场景&#xff1a;注册 登录 想象日常使用…

西南科技大学数字电子技术实验五(用计数器设计简单秒表)FPGA部分

一、实验目的 1.进一步理解用中规模集成计数器构成任意进制计数器的原理。 2.了解计数器的简单应用。 3.进一步学习与非门和译码显示器的使用方法。 4.学会用FPGA实现本实验内容。 二、实验原理 简单秒表 可暂停、复位秒表 三、程序清单(每条语句必须包括注释或在开发…

[Linux] LVS+Keepalived高可用集群部署

一、Keepalived实现原理 1.1 高可用方案 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题。 在一个LVS服务集群中通常有主服务器&#xff08;MASTER&#xff09;和备份服务器&#xff08;BACKUP&#xff09;两种角色…

Jenkins+Docker+Gitee搭建自动化部署平台

目录 服务器准备 Docker安装 yum 包更新到最新 设置yum源 安装docker 启动和开机启动 验证安装是否成功 Jenkins安装 拉取镜像 创建映射目录 运行镜像 运行出错 修正权限 重新运行镜像 新建安全组&#xff0c;放通8080端口 激活Jenkins Jenkins插件 Jenkins全…

【论文解读】Comparing VVC, HEVC and AV1 using Objective and Subjective Assessments

时间&#xff1a;2020 级别&#xff1a;IEEE 机构&#xff1a; IEEE 组织 摘要&#xff1a; 对3种最新的视频编码标准HEVC (High Efficiency video Coding)测试模型HM (High Efficiency video Coding)、amedia video 1 (AV1)和Versatile video Coding测试模型 (VTM)进行了客观和…

UE5 水材质注意要点

1、两个法线反向交替流动&#xff0c;可以去观感假的现象 2、水面延边的透明度低 3、增加水面延边的浪花 4、增加折射 折射要整体质量至少在High才有效果 改为半透明材质没有法线信息&#xff1f; 5、处理反射效果 勾选为true 找到这个放在水域 勾为false&#xff0c;即可有非…

欺骗技术:网络反情报的艺术

网络攻击变得越来越普遍和复杂。例如&#xff0c;2022 年&#xff0c;数据泄露的平均成本高达 445 万美元&#xff0c;显示了这些威胁的严重影响。 这清楚地表明对先进安全方法的需求与日俱增。迅速流行的技术之一是欺骗技术。 与直接阻止或识别威胁的标准安全方法不同&…

VBA之Word应用:利用代码统计文档中的书签个数

《VBA之Word应用》&#xff08;版权10178982&#xff09;&#xff0c;是我推出第八套教程&#xff0c;教程是专门讲解VBA在Word中的应用&#xff0c;围绕“面向对象编程”讲解&#xff0c;首先让大家认识Word中VBA的对象&#xff0c;以及对象的属性、方法&#xff0c;然后通过实…

在 Kubernetes 上部署 Python 3.7、Chrome 和 Chromedriver(版本 114.0.5735.90)的完整指南

一、构建基础镜像 docker build -f /u01/isi/DockerFile . -t thinking_code.com/xhh/crawler_base_image:v1.0.2docker push thinking_code.com/xhh/crawler_base_image:v1.0.2 二、K8s运行Pod 三、DockerFile文件 # 基于镜像基础 FROM python:3.7# 设置代码文件夹工作目录…

Axure中继器的使用实现表格的增删改查的自定义文件

目录 一.认识中继器 1.1.什么中继器 1.2. 中继器的组成 1.3.中继器的使用场景 二.中继器进行增删改查 三.十例表格增删改查 还有Axure这个东西许多东西需要我们去发现&#xff0c;我们需要去细心的研究&#xff0c;我们一起加油吧&#xff01;&#xff01;&#xff01;今…

ASP.NET Core MVC依赖注入理解(极简个人版)

依赖注入 文献来源&#xff1a;《Pro ASP.NET Core MVC》 Adam Freeman 第18章 依赖注入 1 依赖注入原理 所有可能变化的地方都用接口在使用接口的地方用什么实体类通过在ConfigureService中注册解决注册的实体类需要指定在何种生命周期中有效 TransientScopedSingleton 2…

SQL 入门指南:从零开始学习 SQL

当今时代&#xff0c;数据已经成为了我们生活中不可或缺的一部分。无论是企业的经营决策&#xff0c;还是个人的日常消费习惯&#xff0c;都需要通过对数据的收集、分析和应用来实现更好的结果。 而关系型数据库系统&#xff0c;作为最常见的数据存储和管理方式&#xff0c;SQ…

13 v-show指令

概述 v-show用于实现组件的显示和隐藏&#xff0c;和v-if单独使用的时候有点类似。不同的是&#xff0c;v-if会直接移除dom元素&#xff0c;而v-show只是让dom元素隐藏&#xff0c;而不会移除。 在实际开发中&#xff0c;v-show也经常被用到&#xff0c;需要重点掌握。 基本…

广州华锐互动VRAR:利用VR开展新能源汽车触电安全演练,降低培训成本和风险

随着新能源汽车行业的快速发展&#xff0c;相关的安全培训也变得越来越重要。其中&#xff0c;触电急救培训对于保障驾驶员和乘客的安全具有重要意义。传统培训方式存在一些不足&#xff0c;而利用VR技术进行培训则具有很多优势。 利用VR技术开展新能源汽车触电安全演练可以在模…

扩散模型介绍

介绍 AI 绘画中的扩散模型是近年来在计算机视觉和图像生成领域中获得关注的一种深度学习方法。这种模型特别擅长于生成高质量的图像&#xff0c;包括艺术作品和逼真的照片样式的图像。扩散模型的关键思想是通过一个渐进的、可逆的过程将数据&#xff08;在这个场景中是图像&am…

验证码:防范官网恶意爬虫攻击,保障用户隐私安全

网站需要采取措施防止非法注册和登录&#xff0c;验证码是有效的防护措施之一。攻击者通常会使用自动化工具批量注册网站账号&#xff0c;以进行垃圾邮件发送、刷量等恶意活动。验证码可以有效阻止这些自动化工具&#xff0c;有效防止恶意程序或人员批量注册和登录网站。恶意程…

设计模式(三)-结构型模式(5)-外观模式

一、为何需要外观模式&#xff08;Facade&#xff09;? 要实现一个大功能&#xff0c;我们需要将它拆分成多个子系统。然后每个子系统所实现的功能&#xff0c;就由一个称为外观的高层功能模块来调用。这种设计方式就称为外观模式。该模式在开发时常常被使用过&#xff0c;所…

Axure中继器的基本使用

介绍中继器 在 Axure 中&#xff0c;中继器是一种交互设计元素&#xff0c;用于在不同页面之间传递数据或触发特定的事件。它可以帮助模拟真实的用户交互流程和页面之间的传递逻辑&#xff0c;继承关系用于描述两个元件之间的父子关系。通过使用继承关系&#xff0c;您可以创建…