【数据结构】图的最小生成树

server/2024/12/22 14:39:25/



快乐的流畅:个人主页


个人专栏:《C游记》《进击的C++》《Linux迷航》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、最小生成树的概念
  • 二、Kruskal算法
    • 2.1 思想
    • 2.2 实现
  • 三、Prim算法
    • 3.1 思想
    • 3.2 实现
  • 四、Kruskal和Prim的对比

引言

前置知识:【数据结构】并查集
前置知识:【数据结构】图的概念和存储结构

一、最小生成树的概念

最小生成树(Minimum Spanning Tree, MST)图论中的一个重要概念,指的是在一个连通的无向图中,选择一部分边,使得这些边连接所有顶点且边权值之和最小,同时生成的子图仍是一个树结构(没有环)。


按照定义,若连通网络由n个顶点组成,则其生成树必含n个顶点,n-1条边。因此,构造最小生成树有3个准则:

  1. 只能使用该网络的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接n个顶点
  3. 选用的这n-1条边不能构成回路


下面讲解两种经典的最小生成树算法——kruskal算法和prim算法,它们都采用了贪心策略来进行实现。

前置声明:

template<class W>
struct Edge
{int _srci;int _dsti;W _w;Edge(int srci, int dsti, const W& w): _srci(srci), _dsti(dsti), _w(w){}bool operator>(const Edge& e) const{return _w > e._w;}
};template<class V, class W, W W_MAX = INT_MAX, bool Direction = false>
class Graph
{typedef Edge<W> Edge;typedef Graph<V, W, W_MAX, Direction> Self;//...
}

二、Kruskal算法

2.1 思想

Kruskal算法采用全局贪心的策略:

  1. 每次选取图中权值最小的边。
  2. 每次选取完后,判断是否构成回路。若构成,则舍弃这条边。

具体图示如下:

2.2 实现

思路:

  1. 采用优先级队列(小堆),将所有边存入,以便每次选取全图权值最小的边。
  2. 采用并查集,存储已选取的顶点。每次选取边时,判断两侧顶点是否在并查集中,以此判断是否构成回路。
W Kruskal(Self& minTree)
{minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;int n = _edges.size();minTree._edges.resize(n, vector<W>(n, W_MAX));priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (i < j && _edges[i][j] != W_MAX)//无向图,防止重复记录路径{minHeap.push(Edge(i, j, _edges[i][j]));}}}UnionFindSet<V> ufs(n);W total = W();int count = 0;while (!minHeap.empty()){Edge top = minHeap.top();minHeap.pop();int srci = top._srci, dsti = top._dsti;W w = top._w;if (!ufs.InSet(srci, dsti)){minTree._AddEdge(srci, dsti, w);ufs.Union(srci, dsti);total += w;++count;if (count == n - 1){break;}cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << endl;}else{cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << "[构成环]" << endl;}}if (count == n - 1){return total;}else{return W();}
}

细节:

  1. 输入一个空图,输出最小生成树的总权值,并将空图变为最小生成树。
  2. count记录已选边数,若达到n-1,则可直接跳出循环,提高效率。

三、Prim算法

3.1 思想

Prim算法采用局部贪心的策略:

  1. 将已被选择的点看作一个顶点集合,初始状态只有起点在集合中。
  2. 每次在集合周围查找,找到消耗权值最小即可抵达的点,并将其纳入集合。

具体图示如下:

3.2 实现

思路:

  1. 采用优先级队列(小堆),将起始点周围的路径存入,以便每次选取集合周围权值最小的边。
  2. 集合用bool数组表示,每次只有当目标点不在集合中,才将其纳入集合。
  3. 每次将新点纳入集合后,再将新点周围的路径存入优先级队列,依次迭代。
W Prim(Self& minTree, const V& src)
{minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;int n = _edges.size();minTree._edges.resize(n, vector<W>(n, W_MAX));//起始点周围的路径入堆priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;int srci = GetIndex(src);for (int i = 0; i < n; ++i){if (_edges[srci][i] != W_MAX){minHeap.push(Edge(srci, i, _edges[srci][i]));}}vector<bool> S(n, false);S[srci] = true;W total = W();int count = 0;while (!minHeap.empty()){Edge top = minHeap.top();minHeap.pop();int srci = top._srci, dsti = top._dsti;W w = top._w;if (!S[dsti]){minTree._AddEdge(srci, dsti, w);S[dsti] = true;total += w;++count;if (count == n - 1){break;}for (int i = 0; i < n; ++i){if (!S[i] && _edges[dsti][i] != W_MAX)//无向图,防止重复记录路径{minHeap.push(Edge(dsti, i, _edges[dsti][i]));}}cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << endl;}else{cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << "[构成环]" << endl;}}if (count == n - 1){return total;}else{return W();}
}

细节:

  1. 输入一个空图和一个起始点,输出最小生成树的总权值,并将空图变为最小生成树。
  2. count记录已选边数,若达到n-1,则可直接跳出循环,提高效率。

四、Kruskal和Prim的对比

Kruskal 算法Prim 算法
算法类型贪心算法贪心算法
适用图类型稀疏图,特别适合边权值分布较广的图稠密图,特别适合顶点多边多的图
基本思想从边的角度出发,按权值从小到大选择边从顶点出发,每次扩展最小权值的边
时间复杂度O(E log V)O(E log V)
典型应用网络设计问题,边多且分散的图电网、网络设计问题,稠密的图
贪心选择标准每次选择权值最小且不形成环的边每次选择最小的连接权值,扩展已加入的顶点集合

  • Kruskal:适用于稀疏图,因为其从边的角度出发,边的数量相对少时效率更高。
  • Prim:适用于稠密图,因为它每次从顶点出发,逐渐扩展树,对于稠密图(边多的图)效率更高

真诚点赞,手有余香


http://www.ppmy.cn/server/124936.html

相关文章

蓝桥杯—STM32G431RBT6(RTC时钟获取时间和日期)

一、RTC是什么&#xff0c;有什么用&#xff1f; 在 STM32 中&#xff0c;RTC&#xff08;Real-Time Clock&#xff0c;实时时钟&#xff09;主要有以下作用&#xff1a; 时间保持&#xff1a;即使在系统断电情况下&#xff0c;也能持续记录时间。&#xff08;需要纽扣电池供电…

初识C语言(四)

目录 前言 十一、常见关键字&#xff08;补充&#xff09; &#xff08;1&#xff09;register —寄存器 &#xff08;2&#xff09;typedef类型重命名 &#xff08;3&#xff09;static静态的 1、修饰局部变量 2、修饰全局变量 3、修饰函数 十二、#define定义常量和宏…

【架构】前台、中台、后台

文章目录 前台、中台、后台1. 前台&#xff08;Frontend&#xff09;特点&#xff1a;技术栈&#xff1a; 2. 中台&#xff08;Middleware&#xff09;特点&#xff1a;技术栈&#xff1a; 3. 后台&#xff08;Backend&#xff09;特点&#xff1a;技术栈&#xff1a; 示例场景…

STM32常见配置

二. GPIO配置 2.1 初始化 GPIO时钟 使能所需GPIO端口的时钟&#xff1a; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);2.2 配置 GPIO 引脚 创建一个GPIO初始化结构体并配置引脚‘ GPIO_InitTypeDef GPIO_InitStruct;// 配置引脚为推挽输出模式 GPIO_InitStruct…

OJ在线评测系统 前端开发整合开源组件 Monaco Editor 并且开发创建题目页面

前端开发整合Monaco Editor 微软官方的 npm install monaco-editor 下载兼容版本 npm install monaco-editorlatest 代码编辑器 先把编辑器本身安装好monaco-editor 安装插件 npm install monaco-editor-webpack-plugin 这个插件的作用是把我们的代码编译器和webpack打包在…

HTML5 Video标签的属性、方法和事件汇总,以及常用视频插件推荐

&#x1f680; 个人简介&#xff1a;某大型国企资深软件研发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f9…

IMX6UL开发板中断实验(三)

在上一节我们编写完成了中断驱动文件和中断驱动头文件&#xff0c;那么这一讲我们将继续中断实验 下面就是GPIO的中断设置&#xff0c;第一步要设置中断GPIO的触发方式&#xff0c;首先我们先看到寄存器&#xff0c;一共有GPIOx_ICR1和ICR2&#xff0c; 图如上&#xff0c;ICR1…

Spark_UDF处理缺失值或空值

在Apache Spark中&#xff0c;处理空值&#xff08;null&#xff09;是一个常见的需求&#xff0c;尤其是在使用用户定义的函数&#xff08;UDF&#xff09;时。 在UDF内部检查空值&#xff1a;在UDF中&#xff0c;你应该检查输入值是否为空&#xff0c;并相应地处理。例如&am…