DS图(中)(19)

文章目录

  • 前言
  • 一、图的遍历
    • 广度优先遍历
    • 深度优先遍历
  • 二、最小生成树
    • Kruskal算法
    • Prim算法
    • 两种方法对比
  • 总结


前言

  承上启下,我们来学习下图的中篇!!!


一、图的遍历

  图的遍历指的是遍历图中的顶点,主要有 广度优先遍历 和 深度优先遍历 两种方式。

广度优先遍历

  广度优先遍历又称BFS,其遍历过程类似于二叉树的层序遍历,从起始顶点开始一层一层向外进行遍历。

在这里插入图片描述
广度优先遍历的实现:

  • 广度优先遍历需要借助一个队列和一个标记数组,利用队列先进先出的特点实现一层一层向外遍历,利用标记数组来记录各个顶点是否被访问过。
  • 刚开始时将起始顶点入队列,并将起始顶点标记为访问过,然后不断从队列中取出顶点进行访问,并判断该顶点是否有邻接顶点,如果有邻接顶点并且该邻接顶点没有被访问过,则将该邻接顶点入队列,并在入队列后立即将该邻接顶点标记为访问过。
		void BFS(const V& src)				
		{
			size_t srci = GetVertexIndex(src);

			// 队列和标记数组
			queue<int> q;
			vector<bool> visited(_vertexs.size(), false);

			q.push(srci);
			visited[srci] = true;
			int levelSize = 1;

			size_t n = _vertexs.size();
			while (!q.empty())
			{
				// 一层一层出
				for (int i = 0; i < levelSize; ++i)
				{
					int front = q.front();
					q.pop();
					
					cout << front << ":" << _vertexs[front] << " ";
					
					// 把front顶点的邻接顶点入队列
					for (size_t i = 0; i < n; ++i)
					{
						if (_matrix[front][i] != MAX_W)
						{
							if (visited[i] == false)
							{
								q.push(i);
								visited[i] = true;
							}
						}
					}
				}
				cout << endl;

				levelSize = q.size();
			}

			cout << endl;
		}
  • 为了防止顶点被重复加入队列导致死循环,因此需要一个标记数组,当一个顶点被访问过后就不应该再将其加入队列了。

  • 如果当一个顶点从队列中取出访问时才再将其标记为访问过,也可能会存在顶点被重复加入队列的情况,比如当图中的顶点B出队列时,顶点C作为顶点B的邻接顶点并且还没有被访问过(顶点C还在队列中),此时顶点C就会再次被加入队列,因此最好在一个顶点被入队列时就将其标记为访问过。

  • 如果所给图不是一个连通图,那么从一个顶点开始进行广度优先遍历,无法遍历完图中的所有顶点,这时可以遍历标记数组,查看哪些顶点还没有被访问过,对于没有被访问过的顶点,则从该顶点处继续进行广度优先遍历,直到图中所有的顶点都被访问过。

深度优先遍历

  深度优先遍历又称DFS,其遍历过程类似于二叉树的先序遍历,从起始顶点开始不断对顶点进行深入遍历。

在这里插入图片描述
深度优先遍历的实现:

  1. 深度优先遍历可以通过递归实现,同时也需要借助一个标记数组来记录各个顶点是否被访问过。
  2. 从起始顶点处开始进行递归遍历,在遍历过程中先对当前顶点进行访问,并将其标记为访问过,然后判断该顶点是否有邻接顶点,如果有邻接顶点并且该邻接顶点没有被访问过,则递归遍历该邻接顶点。
		void _DFS(size_t srci, vector<bool>& visited)
		{
			cout << srci << ":" << _vertexs[srci] << endl;
			visited[srci] = true;

			// 找一个srci相邻的没有访问过的点,去往深度遍历
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				if (_matrix[srci][i] != MAX_W && visited[i] == false)
				{
					_DFS(i, visited);
				}
			}

		}

		void DFS(const V& src)
		{
			size_t srci = GetVertexIndex(src);
			vector<bool> visited(_vertexs.size(), false);

			_DFS(srci, visited);
		}
  • 如果所给图不是一个连通图,那么从一个顶点开始进行深度优先遍历,无法遍历完图中的所有顶点,这时可以遍历标记数组,查看哪些顶点还没有被访问过,对于没有被访问过的顶点,则从该顶点处继续进行深度优先遍历,直到图中所有的顶点都被访问过。

二、最小生成树

关于最小生成树

  • 一个连通图的最小连通子图称为该图的生成树,若连通图由 n 个顶点组成,则其生成树必含 n 个顶点和 n−1 条边,最小生成树指的是一个图的生成树中,总权值最小的生成树。

  • 连通图中的每一棵生成树都是原图的一个极大无环子图,从其中删去任何一条边,生成树就不再连通,在其中引入任何一条新边,都会形成一条回路。

  1. 对于各个顶点来说,除了第一个顶点之外,其他每个顶点想要连接到图中,至少需要一条边使其连接进来,所以由 n 个顶点的连通图的生成树有 n 个顶点和 n−1 条边。

  2. 对于生成树来说,图中的每个顶点已经连通了,如果再引入一条新边,那么必然会使得被新边相连的两个顶点之间存在一条直接路径和一条间接路径,即形成回路。

  3. 最小生成树是图的生成树中总权值最小的生成树,生成树是图的最小连通子图,而连通图是无向图的概念,有向图对应的是强连通图,所以最小生成树算法的处理对象都是无向图。

综上,我们可以得出以下生成 最小生成树 的准则

  1. 只能使用图中的边来构造最小生成树。
  2. 只能使用恰好 n−1 条边来连接图中的 n 个顶点。
  3. 选用的 n−1 条边不能构成回路。

构造最小生成树的算法有 Kruskal算法 和 Prim算法 ,这两个算法都采用了逐步求解的贪心策略。

Kruskal算法

Kruskal算法的基本思想如下:

  1. 构造一个含 n 个顶点、不含任何边的图作为最小生成树,对原图中的各个边按权值进行排序。
  2. 每次从原图中选出一条最小权值的边,将其加入到最小生成树中,如果加入这条边会使得最小生成树中构成回路,则重新选择一条边。
  3. 按照上述规则不断选边,当选出 n−1 条合法的边时,则说明最小生成树构造完毕,如果无法选出 n−1 条合法的边,则说明原图不存在最小生成树。

在这里插入图片描述

还记得我们之前学过的并查集么,如果忘记的话,快去复习一下吧!

  • 根据原图设置最小生成树的顶点集合,以及顶点与下标的映射关系,开辟最小生成树的邻接矩阵空间,并将矩阵中的值初始化为 MAX_W ,表示刚开始时最小生成树中不含任何边。

  • 遍历原图的邻接矩阵,按权值将原图中的所有边添加到优先级队列(小堆)中,为了避免重复添加相同的边,在遍历原图的邻接矩阵时只应该遍历矩阵的一半。

  • 使用一个并查集来辅助判环操作,刚开始时图中的顶点各自为一个集合,当两个顶点相连时将这两个顶点对应的集合进行合并,使得连通的顶点在同一个集合,这样通过并查集就能判断所选的边是否会使得最小生成树中构成回路,如果所选边连接的两个顶点本就在同一个集合,那么加入这条边就会构成回路。

  • 使用 count 和 totalWeight 分别记录所选边的数量和最小生成树的总权值,当 count 的值等于 n−1 时则停止选边,此时可以将最小生成树的总权值作为返回值进行返回。

  • 每次选边时从优先级队列中获取一个权值最小的边,并通过并查集判断这条边连接的两个顶点是否在同一个集合,如果在则重新选边,如果不在则将这条边添加到最小生成树中,并将这条边连接的两个顶点对应的集合进行合并,同时更新 count 和 totalWeight 的值。

  • 当选边结束时,如果 count 的值等于 n−1 ,则说明最小生成树构造成功,否则说明原图无法构造出最小生成树。

		typedef Graph<V, W, MAX_W, Direction> Self;
		struct Edge
		{
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci, size_t dsti, const W& w)
				:_srci(srci)
				,_dsti(dsti)
				,_w(w)
			{}

			bool operator>(const Edge& e) const
			{
				return _w > e._w;
			}
		};

		W Kruskal(Self& minTree)
		{
			size_t n = _vertexs.size();

			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}

			priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
					if (i < j && _matrix[i][j] != MAX_W)
					{
						minque.push(Edge(i, j, _matrix[i][j]));
					}
				}
			}

			// 选出 n - 1 条边
			int size = 0;
			W totalW = W();
			UnionFindSet ufs(n);
			
			while (!minque.empty())
			{
				Edge min = minque.top();
				minque.pop();

				if (!ufs.InSet(min._srci, min._dsti))
				{
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] <<":"<<min._w << endl;
					minTree._AddEdge(min._srci, min._dsti, min._w);
					ufs.Union(min._srci, min._dsti);
					++size;
					totalW += min._w;
				}
				else
				{
					//cout << "构成环:";
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
			}

			if (size == n - 1)
			{
				return totalW;
			}
			else
			{
				return W();
			}
		}
  • 在获取图的最小生成树时,会以无参的方式定义一个最小生成树对象,然后用原图对象调用上述 Kruskal函数 ,通过输出型参数的方式获取原图的最小生成树,由于我们定义了一个带参的构造函数,使得编译器不再生成默认构造函数,因此需要通过 default 关键字强制生成 Graph类 的默认构造函数。

  • 一条边包含两个顶点和边的权值,可以定义一个Edge结构体来描述一条边,结构体内包含边的源顶点和目标顶点的下标以及边的权值,在使用优先级队列构造小堆结构时,需要存储的对象之间能够支持 > 运算符操作,因此需要对Edge结构体的 > 运算符进行重载,将其重载为边的权值的比较。

  • 当选出的边不会构成回路时,需要将这条边插入到最小生成树对应的图中,此时已经知道了这条边的源顶点和目标顶点对应的下标,可以在 Graph类 中新增一个 _AddEdge 子函数,该函数支持通过源顶点和目标顶点的下标向图中插入边,而 Graph类 中原有的 AddEdge函数 可以复用这个 _AddEdge 子函数。

  • 最小生成树不一定是唯一的,特别是当原图中存在很多权值相等的边的时候。

  • 上述代码中通过优先级队列构造小堆来依次获取权值最小的边,你也可以通过其他排序算法按权值对边进行排序,然后按权值从小到大依次遍历各个边进行选边操作。

Prim算法

Prim算法的基本思想如下:

  1. 构造一个含 n 个顶点、不含任何边的图作为最小生成树,将图中的顶点分为两个集合,forest 集合中的顶点是已经连接到最小生成树中的顶点,remain 集合中的顶点是还没有连接到最小生成树中的顶点,刚开始时 forest 集合中只包含给定的起始顶点。

  2. 每次从连接 forest 集合与 remain 集合的所有边中选出一条权值最小的边,将其加入到最小生成树中,由于选出来的边对应的两个顶点一个属于 forest 集合,另一个属于 remain 集合,因此这种方法是天然不会构成回路的。

  3. 按照上述规则不断选边,当选出 n−1 条边时,所有的顶点都已经加入到了 forest 集合,此时最小生成树构造完毕,如果无法选出 n−1 条边,则说明原图不存在最小生成树。

在这里插入图片描述

  • 根据原图设置最小生成树的顶点集合,以及顶点与下标的映射关系,开辟最小生成树的邻接矩阵空间,并将矩阵中的值初始化为 MAX_W ,表示刚开始时最小生成树中不含任何边。

  • 使用一个 forest 数组来表示各个顶点是否在 forest 集合中,刚开始时只有起始顶点在 forest 集合中,并将所有从起始顶点连接出去的边加入优先级队列(小堆),这些边就是刚开始时连接 forest 集合与 remain 集合的边。

  • 使用 count 和 totalWeight 分别记录所选边的数量和最小生成树的总权值,当 count 的值等于 n−1 时则停止选边,此时将最小生成树的总权值作为返回值进行返回。

  • 每次选边时从优先级队列中获取一个权值最小的边,将这条边添加到最小生成树中,并将这条边的目标顶点加入 forest 集合中,同时更新 count 和 totalWeight 的值。

  • 此外,还需要将从这条边的目标顶点连接出去的边加入优先级队列,但是需要保证加入的边的目标顶点不能在 forest 集合,否则后续选出源顶点和目标顶点都在 forest 集合的边就会构成回路。

  • 需要注意的是,每次从优先级队列中选出一个权值最小的边时,还需要保证选出的这条边的目标顶点不在 forest 集合中,避免构成回路。虽然向优先级队列中加入边时保证了加入的边的目标顶点不在 forest 集合中,但经过后续不断的选边,可能会导致之前加入优先级队列中的某些边的目标顶点也被加入到了 forest 集合中。

  • 当选边结束时,如果 count 的值等于 n−1 ,则说明最小生成树构造成功,否则说明原图无法构造出最小生成树。

		W Prim(Self& minTree, const V& src)
		{
			size_t srci = GetVertexIndex(src);
			size_t n = _vertexs.size();

			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}

			/*set<int> X;
			set<int> Y;
			X.insert(srci);
			for (size_t i = 0; i < n; ++i)
			{
				if (i != srci)
				{
					Y.insert(i);
				}
			}*/

			vector<bool> X(n, false);
			vector<bool> Y(n, true);
			X[srci] = true;
			Y[srci] = false;

			// 从X->Y集合中连接的边里面选出最小的边
			priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
			// 先把srci连接的边添加到队列中
			for (size_t i = 0; i < n; ++i)
			{
				if (_matrix[srci][i] != MAX_W)
				{
					minq.push(Edge(srci, i, _matrix[srci][i]));
				}
			}

			cout << "Prim开始选边" << endl;
			size_t size = 0;
			W totalW = W();
			while (!minq.empty())
			{
				Edge min = minq.top();
				minq.pop();

				// 最小边的目标点也在X集合,则构成环
				if (X[min._dsti])
				{
					//cout << "构成环:";
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
				else
				{
					minTree._AddEdge(min._srci, min._dsti, min._w);
					//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
					X[min._dsti]= true;
					Y[min._dsti] = false;
					++size;
					totalW += min._w;
					if (size == n - 1)
						break;

					for (size_t i = 0; i < n; ++i)
					{
						if (_matrix[min._dsti][i] != MAX_W && Y[i])
						{
							minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
						}
					}
				}	
			}

			if (size == n - 1)
			{
				return totalW;
			}
			else
			{
				return W();
			}
		}

两种方法对比

  • Prim算法构造最小生成树的思想在选边时是不需要判环,但上述利用优先级队列实现的过程中仍需判环,如果在每次选边的时候能够通过某种方式,从连接 forest 集合和 remain 集合的所有边中选出权值最小的边,那么就无需判环,但这两个集合中的顶点是不断在变化的,每次选边时都遍历连接两个集合的所有边,该过程的时间复杂度较高(达到O(N^3)的级别),所以我们采用 vector< bool > 的方法来表示两个集合,进而解决了判环的问题
  • Kruskal算法本质是一种全局的贪心,每次选边时都是在所有边中选出权值最小的边,而Prim算法本质是一种局部的贪心,每次选边时是从连接 forest 集合和 remain 集合的所有边中选出权值最小的边。

总结

  困了,接下来还有最短路径问题,就等早上起来再写吧!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

112,【4】攻防世界 web weak_auth

之前做过&#xff0c;回顾 进入靶场 输入admin 123456 不是&#xff0c;这也行&#xff0c;什么闭合方式&#xff0c;注释符都没用上 反而不自然了 不过输入admin 123456 纯属个人习惯 假如我没那么输&#xff0c;或者用户名&#xff0c;密码不是这两个&#xff0c;我该怎…

蓝桥杯更小的数(区间DP)

题目描述 小蓝有一个长度均为 n 且仅由数字字符 0 ∼ 9 组成的字符串&#xff0c;下标从 0 到 n − 1&#xff0c;你可以将其视作是一个具有 n 位的十进制数字 num&#xff0c;小蓝可以从 num 中选出一段连续的子串并将子串进行反转&#xff0c;最多反转一次。小蓝想要将选出的…

109,【1】攻防世界 web 题目名称-文件包含

进入靶场 直接显示源代码 提示我们通过get方式传递名为filename的参数&#xff0c;同时给出了文件名check.php filenamecheck.php 显示使用了正确的用法&#xff0c;错误的方法 filename./check.php 还是一样的回显 傻了&#xff0c;题目名称是文件包含&#xff0c;需要用到…

71.StackPanel黑白棋盘 WPF例子 C#例子

就是生成黑白棋盘&#xff0c;利用该控件能自动排列的功能。用一个横向的StackPanel嵌套纵向的StackPanel&#xff0c;然后在里面添加设定好长和高的矩形。 因为StackPanel是按照控件的大小展示的。所以如果不设置长和宽。就会显示不出矩形。 <StackPanel Orientation"…

DeepSeek之python实现API应用

先创建一个API KEY https://platform.deepseek.com/api_keys python脚本实现 # Please install OpenAI SDK first: `pip3 install openai`from openai import OpenAIclient = OpenAI(api_key="", base_url="https://api.deepseek.com")response = cli…

MySQL中like模糊查询如何优化?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL中like模糊查询如何优化&#xff1f;】面试题。希望对大家有帮助&#xff1b; MySQL中like模糊查询如何优化&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在MySQL中&#xff0c;LIKE模糊查询通常会影…

通过docker安装部署deepseek以及python实现

前提条件 Docker 安装:确保你的系统已经安装并正确配置了 Docker。可以通过运行 docker --version 来验证 Docker 是否安装成功。 网络环境:保证设备有稳定的网络连接,以便拉取 Docker 镜像和模型文件。 步骤一:拉取 Ollama Docker 镜像 Ollama 可以帮助我们更方便地管理…

制作PE启动盘(内含Win11 iso镜像)

前言 本文用于记录制作PE启动盘过程&#xff0c;学习记录用&#xff0c;如有不对请指出&#xff0c;谢谢&#xff01; 参考视频&#xff1a; 1. 微PE下载&#xff1a;https://www.bilibili.com/video/BV1vT4y1n7JX/?spm_id_from333.788.top_right_bar_window_history.conte…

128陷阱

首先我们了解一下关于包装器类型 java是面向对象的语言&#xff0c;但基本类型并不是面向对象的&#xff0c;从而出现了包装器类型&#xff0c;并且包装器添加了更多的属性和方法。如我们在使用集合类型Collection的时候就一定要使用包装类型而非基本类型&#xff0c;它相当于将…

javaEE-9.HTML入门

目录 一.什么是html 二.认识html标签 1.标签的特点: 2.html文件基本结构 3.标签的层次结构 三、html工具 四、创建第一个文件 五.html常见标签 1标题标签h1-h6 2.段落标签:p 3.换行标签:br 4.图片标签:img 图片路径有1三种表示形式: 5.超链接:a 链接的几种形式: …

开源 GPU 集群管理器 GPUStack

GPUStack 是一个用于运行 AI 模型的开源 GPU 集群管理器。 项目地址&#xff1a;gpustack/gpustack: Manage GPU clusters for running AI modelshttps://github.com/gpustack/gpustackhttps://github.com/gpustack/gpustackhttps://github.com/gpustack/gpustack 核心特性 广…

电子电气架构 --- 汽车电子拓扑架构的演进过程

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

【Elasticsearch】range aggregation

Elasticsearch 的Range Aggregation是一种强大的桶聚合&#xff08;Bucket Aggregation&#xff09;工具&#xff0c;用于将文档按照数值范围进行分组&#xff0c;从而实现对数据的分段分析。以下是关于 Range Aggregation 的详细说明&#xff1a; 1.Range Aggregation 的基本概…

AI测试工程师成长指南:以DeepSeek模型训练为例

目录 引言&#xff1a;AI测试工程师的使命与挑战成长日记&#xff1a;从测试小白到AI测试专家核心能力&#xff1a;AI测试工程师的必备素养知识体系&#xff1a;技术栈与技能图谱AI测试工具全景&#xff1a;以DeepSeek为核心的工具链实战训练模式&#xff1a;以DeepSeek模型迭…

Spring Boot整合MQTT

MQTT是基于代理的轻量级的消息发布订阅传输协议。 1、下载安装代理 进入mosquitto下载地址&#xff1a;Download | Eclipse Mosquitto&#xff0c;进行下载&#xff0c;以win版本为例 下载完成后&#xff0c;在本地文件夹找到下载的代理安装文件 使用管理员身份打开安装 安装…

Elasticsearch 开放推理 API 增加了 Azure AI Studio 支持

作者&#xff1a;来自 Elastic Mark Hoy Elasticsearch 开放推理 API 现已支持 Azure AI Studio。在此博客中了解如何将 Azure AI Studio 功能与 Elasticsearch 结合使用。 作为我们持续致力于为 Microsoft Azure 开发人员提供他们选择的工具的一部分&#xff0c;我们很高兴地宣…

【EdgeAI实战】(2)STM32 AI 扩展包的安装与使用

【EdgeAI实战】&#xff08;1&#xff09;STM32 边缘 AI 生态系统 【EdgeAI实战】&#xff08;2&#xff09;STM32 AI 扩展包的安装与使用 【EdgeAI实战】&#xff08;2&#xff09;STM32 AI 扩展包的安装与使用 1. STM32Cube.AI 简介1.1 STM32Cube.AI 简介1.2 X-CUBE-AI 内核引…

MySQL的 MVCC详解

MVCC是多版本并发控制&#xff0c;允许多个事务同时读取和写入数据库&#xff0c;而无需互相等待&#xff0c;从而提高数据库的并发性能。 在 MVCC 中&#xff0c;数据库为每个事务创建一个数据快照。每当数据被修改时&#xff0c;MySQL不会立即覆盖原有数据&#xff0c;而是生…

【电脑系统】电脑突然(蓝屏)卡死发出刺耳声音

文章目录 前言问题描述软件解决方案尝试硬件解决方案尝试参考文献 前言 在 更换硬盘 时遇到的问题&#xff0c;有时候只有卡死没有蓝屏 问题描述 更换硬盘后&#xff0c;电脑用一会就卡死&#xff0c;蓝屏&#xff0c;显示蓝屏代码 UNEXPECTED_STORE_EXCEPTION 软件解决方案…

SpringAI系列 - 使用LangGPT编写高质量的Prompt

目录 一、LangGPT —— 人人都可编写高质量 Prompt二、快速上手2.1 诗人 三、Role 模板3.1 Role 模板3.2 Role 模板使用步骤3.3 更多例子 四、高级用法4.1 变量4.2 命令4.3 Reminder4.4 条件语句4.5 Json or Yaml 方便程序开发 一、LangGPT —— 人人都可编写高质量 Prompt La…