【算法与图】通向高效解决方案的钥匙

在这里插入图片描述

文章目录

  • 遍历算法
    • BFS(广度优先遍历)
      • 1. 什么是 BFS?
      • 2. 特点和应用
      • 3. BFS 示例
    • DFS(深度优先搜索)
      • 1. 什么是 DFS?
      • 2. DFS 的基本步骤
      • 3. 特点
      • 4. DFS 的应用
      • 5. DFS 示例
  • 最小生成树问题
    • 1. 什么是最小生成树?
    • 2. 最小生成树的基本特性
    • 3. 应用场景
    • 4. 最小生成树的算法
    • 5. 最小生成树的图示:
  • 最小生成树算法
    • Kruskal算法
      • 1. 什么是Kruskal算法?
      • 2. 算法步骤
      • 3. 算法复杂度
      • 4. 示例
      • 5.思路及代码
    • Prim算法
      • 1. 什么是Prim算法?
      • 2. 算法步骤
      • 3. 算法复杂度
      • 4. 示例
      • 5.思想及代码
  • 结尾总结

遍历算法

BFS(广度优先遍历)

1. 什么是 BFS?

BFS(广度优先搜索)是一种图的遍历算法,用于从一个起始节点出发,逐层访问图中的所有节点。其基本流程如下:

  1. 起始节点:选择一个节点作为起点。
  2. 队列:使用队列(FIFO)来保存待访问的节点。
  3. 访问过程
    • 将起始节点加入队列并标记为已访问。
    • 当队列不为空时:
      • 从队列中取出一个节点,访问该节点。
      • 将该节点的所有未访问邻居节点加入队列并标记为已访问。
  4. 层级遍历:BFS 会先访问距离起始节点最近的节点,然后逐层向外扩展,直到所有可以访问的节点都被访问。

2. 特点和应用

  • 最短路径:在无权图中,BFS 可以找到从起始节点到其他节点的最短路径。
  • 图的连通性:可以用来判断图的连通性,即判断两个节点是否在同一连通分量中。
  • 应用:广泛应用于网络流、社交网络分析、最短路径问题、迷宫求解等领域。

3. BFS 示例

假设我们有一个无向图,节点间的连接如下:

在这里插入图片描述
应该如何实现这种算法呢?
首先我们应该用一个队列来维护节点,进入BFS这个接口的时候,我们应该传入从哪个节点开始进行BFS。
然后用一个队列来维护节点,先将第一个节点push进队列中,然后将这个节点输出之后,先将这个节点保存起来然后再把这个节点pop掉,然后将与这个节点相连的节点push进队列(注意:这里可能会出现重复的节点,比如我们拿上面的例子为例,第一次push的时候我们将C节点已经push进队列了,但是第二层访问完了之后,到第三层的时候,B和C相连还会遍历一次C,所以这里我们应该用一个vector进行标记,标记这个节点被访问过没有),进入循环的条件是队列是否为空。
代码展示:

void BFS(const V& src)
{
	size_t srci = GetVertexIndex(src);
	queue<int> q;
	q.push(srci);//队列
	vector<bool> visited(_vertexs.size(), false);//标记数组
	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();
			cout << front << ':' << _vertexs[front] << ' ';
			q.pop();
			//把front顶点的邻接顶点入队列
			for (size_t i = 0;i < _vertexs.size();i++)
			{
				if (_matrix[front][i] != MAX_W && visited[i] == false)
				{
					q.push(i);
					visited[i] = true;
				}
			}
		}
		cout << endl;
		//levelzie是下一层的数据个数
		levelsize = q.size();
	}
}

DFS(深度优先搜索)

1. 什么是 DFS?

DFS(深度优先搜索)是一种图的遍历算法,从起始节点开始,尽可能深入探索每个分支,直到无法再继续,然后回溯到上一个节点,继续探索其他分支。它适用于有向图和无向图。

2. DFS 的基本步骤

  1. 起始节点:选择一个节点作为起点。
  2. 深入探索:访问起始节点,并标记为已访问。
  3. 递归访问:对当前节点的每个未访问邻居节点递归进行深度优先搜索。
  4. 回溯:如果当前节点的所有邻居都被访问,则回溯到上一个节点,继续深度搜索其他分支。

3. 特点

  • 优先深入:DFS 尽可能先访问一个节点的最深分支,再逐步回溯到较浅的分支。
  • 非最短路径:与 BFS 不同,DFS 不保证找到最短路径,因为它按深度优先进行搜索。
  • 应用广泛:DFS 可用于检测图的连通性、拓扑排序、寻找路径和检测环。

4. DFS 的应用

  • 路径搜索:可以用于寻找图中从一个节点到另一个节点的路径。
  • 图的连通性:判断图中的连通分量。
  • 拓扑排序:用于有向无环图(DAG)的节点排序。
  • 检测环:可以检测图中是否存在环。

5. DFS 示例

在这里插入图片描述
DFS和BFS一样,还是给定一个起点,从这个起点开始进行,但是DFS的方式和BFS完全不同,DFS是一条路走到黑,从当前节点一直走,走到不能走为止,当走到不能走时,进行回溯,回溯到上一个岔口,然后向刚刚没有走过的路口继续走,走到尽头的时候又进行回溯,就一直这样递归,直到把所有节点遍历完为止。

代码展示:

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);
	
}

注意:DFS中也需要用一个bool数组进行标记,当前位置是否被访问过。

最小生成树问题

1. 什么是最小生成树?

最小生成树是一个图的子集,包含图中的所有节点,并且是连通的,同时边的总权重最小。最小生成树的特点是没有回路,并且连接了图中的所有节点。

2. 最小生成树的基本特性

  • 包含所有节点:最小生成树包含图中的所有顶点。
  • 边的权重总和最小:在所有可能的生成树中,其边权重之和是最小的。
  • 无环图:最小生成树是一个无环的连通图。

3. 应用场景

  • 网络设计:如计算机网络、交通网络的最优连接。
  • 电路设计:用于布线问题,减少电缆长度。
  • 聚类分析:在数据科学中,用于分类和分组。

4. 最小生成树的算法

常用的求解最小生成树的算法有:

  1. Kruskal 算法

    • 通过选择边的方式逐步构建最小生成树,优先选择权重最小的边,确保不形成回路。
  2. Prim 算法

    • 从一个起始节点开始,逐步扩展生成树,选择连接已包含节点和未包含节点的最小权重边。

5. 最小生成树的图示:

在这里插入图片描述

下面的图就是上面的图的最小生成树的其中之一。最小生成树是不止一个的。如果我们选择上面那个8,最小生成树又不一样。
在这里插入图片描述

最小生成树算法

Kruskal算法

1. 什么是Kruskal算法?

克鲁斯卡尔算法是一种用于求解最小生成树(MST)的贪心算法。它通过选择边的方式逐步构建最小生成树,优先选择权重最小的边,并确保不形成回路。

2. 算法步骤

克鲁斯卡尔算法的基本步骤如下:

  1. 排序边:将图中的所有边按权重从小到大排序。
  2. 初始化:创建一个空的生成树,并初始化一个并查集(Union-Find)结构,用于检测图中的环。
  3. 选择边
    • 从权重最小的边开始,依次考虑每条边。
    • 对于每条边 (u, v),检查 uv 是否在同一连通分量中(使用并查集)。
    • 如果不在同一连通分量中,加入该边到生成树,并将 uv 的连通分量合并。
  4. 结束条件:当生成树中的边数等于 V-1V 为节点数)时,算法结束。

3. 算法复杂度

  • 时间复杂度O(E log E),其中 E 是边的数量,主要由排序边的时间决定。
  • 空间复杂度O(V),用于存储并查集。

4. 示例

在这里插入图片描述

5.思路及代码

根据克鲁斯卡尔算法的步骤:

  1. 排序边,我们可以用优先级队列来对边进行排序,但是这个边不能只有边,应该还需要边两端的顶点,所以我们需要把这个封装成一个结构体,也就是边集,还有一个问题就是排序,优先级队列的排序默认是从大到小,所以我们还需要写一个比较的逻辑进行比较。
  2. 初始化:初始化我们只需要将最小生成树初始化为原图的大小即可。大小和原图一模一样,顶点映射下标的关系也和原图一模一样。
  3. 选择边:在选择边时,我们只需要将存在优先级队列中的边取出来即可,但是在选择边时需要检查一下这个边加入之后是否会形成环,这时就可以利用并查集,因为并查集可以高效管理集合,我们开辟一个并查集的大小是顶点个数的并查集,如果这条边选了就将这两个顶点的集合合并,如果会形成环的话,那么对应的两个顶点肯定在一个集合当中。所以我们只需要判断这两个顶点是否在一个集合当中即可,如果没在一个集合当中,就将这条边给最小生成树,并且将这两个顶点的集合合并,还需要一个累加权值的变量记录总的权值大小。
  4. 结束条件:用size记录边数的大小,当边的条数等于顶点的个数-1的时候就是结束的时候。

代码展示:

W Kruskal(Self& minTree)
{
	size_t n = _vertexs.size();
	minTree._vertexs = _vertexs;
	minTree._indexMap = _indexMap;
	minTree._matrix.resize(n);
	for (auto& e : minTree._matrix) e.resize(n, MAX_W);
	//优先级队列,优先级默认是大的优先级高,所以这里要控制
	priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
	for (int i = 0;i < n;i++)
	{
		for (int j = 0;j < n;j++)
		{
			//i<j保证只访问一次
			if (i < j && _matrix[i][j] != MAX_W);
			{
				//将edge插入进去,由于无向图会出现重复添加的情况,所以无向图走一半即可
				minq.push(Edge(i, j, _matrix[i][j]));
			}
		}
	}
	//选出n-1条边
	//开辟一个n个顶点的并查集
	size_t size = 0;
	//总的权值
	W totalW = W();
	UnionFindset ufs(n);
	while (!minq.empty()) 
	{
		//选择一条边
		Edge eg = minq.top();
		minq.pop();
		//观察两个顶点是否在同一个集合
		if (!ufs.IsInSet(eg._srci, eg._dsti))
		{
			cout << _vertexs[eg._dsti] << "->" << _vertexs[eg._srci] << ':' << eg._w << endl;
			//将边添加到mintree中
			minTree._AddEdge(eg._srci, eg._dsti, eg._w);
			//将顶点添加到并查集当中
			ufs.Union(eg._srci, eg._dsti);
			totalW += eg._w;
			++size;
		}
	}
	if (size == n - 1) return totalW;
	else return W();
}

Prim算法

1. 什么是Prim算法?

普利姆算法是一种用于求解最小生成树(MST)的贪心算法。它从一个节点开始,通过逐步选择连接已访问节点和未访问节点的最小权重边来扩展生成树,直到所有节点都被包含。

2. 算法步骤

普利姆算法的基本步骤如下:

  1. 选择起始节点:从图中的任意一个节点开始(通常是第一个节点)。
  2. 初始化:将起始节点加入生成树,并将它的所有邻边放入一个优先队列(最小堆),按边的权重排序。
  3. 选择最小权重边:从优先队列中取出权重最小的边,并检查其连接的节点是否已在生成树中。
    • 如果该节点已在生成树中,忽略这条边。
    • 如果该节点不在生成树中,将该节点和边加入生成树,并将新节点的所有邻边加入优先队列。
  4. 重复:不断从优先队列中选取最小权重边,直到生成树包含所有节点。

3. 算法复杂度

  • 时间复杂度O(E log V),其中 E 是边的数量,V 是节点数量,主要由最小堆操作决定。
  • 空间复杂度O(V^2)

4. 示例

在这里插入图片描述

5.思想及代码

普林姆算法和克里姆林算法还是有很大的差别的,普林姆算法需要起始点,然后将起始点相连最小的边入到边集当中。
在这里插入图片描述
假设这个图我们以a点为例,和a相连的边是b和h点,ab边明显小于ah边,所以我们选择ab边
在这里插入图片描述
这里引入了b点,所以我们的选择是bc边,bh边还有ah边,很明显这里我们选择bc边也可以,ah边也可以,所以我们就选择ah边吧。
在这里插入图片描述
这里我们可以选择的边种最小的是hg边。
在这里插入图片描述
接下来我们肯定选择gf,因为gf最小
在这里插入图片描述
最后可以推出最小生成树
在这里插入图片描述
Prim算法的思路很简单,实现过程我们还是用优先级队列,但是在选择的时候我们需要判断一下是否形成环。
代码展示:

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 (auto& e : minTree._matrix)e.resize(n, MAX_W);
	//选过的顶点
	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;
	for (size_t i = 0;i < _vertexs.size();i++)
	{
		//把srci连接的边添加到队列中
		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();
		//如果最小边的目标点也在起点集合
		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;
			size++;
			totalW += min._w;
			if (size == n - 1) break;
			//将dsti添加到X集合
			X[min._dsti] = true;
			Y[min._dsti] = false;
			for (size_t i = 0;i < _vertexs.size();i++)
			{
				//将目标点连接的边添加到集合当中并且需要判断目标点是否已经在集合当中
				if (_matrix[min._dsti][i] != MAX_W && X[i] == false)minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
			}
		}
	}
	if (size == n - 1)return totalW;
	else return W();
}

结尾总结

通过本文的讲解,我们深入了解了图论中的三种经典算法:广度优先搜索(BFS)、深度优先搜索(DFS)和最小生成树(Kruskal算法和Prim算法)。这些算法在计算机科学、数据分析、人工智能等领域有着广泛的应用。

从简单的图遍历到复杂的网络优化问题,这些算法都展现了其强大的解决问题能力。然而,图论是一个庞大的领域,还有许多更深入、更复杂的算法等待我们去探索。例如,拓扑排序、强连通分量、最短路径问题等。

希望本文能为你打开图论算法的大门,激发你对算法学习的兴趣。在未来的学习中,我们可以继续深入研究图论算法,并将其应用到实际的项目中。

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

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

相关文章

【mmengine】优化器封装(OptimWrapper)(入门)优化器封装 vs 优化器

MMEngine 实现了优化器封装&#xff0c;为用户提供了统一的优化器访问接口。优化器封装支持不同的训练策略&#xff0c;包括混合精度训练、梯度累加和梯度截断。用户可以根据需求选择合适的训练策略。优化器封装还定义了一套标准的参数更新流程&#xff0c;用户可以基于这一套流…

虚拟机三种网络模式详解

在电脑里开一台虚拟机&#xff0c;是再常见不过的操作了。无论是用虚拟机玩只有旧版本系统能运行的游戏&#xff0c;还是用来学习Linux、跑跑应用程序都是很好的。而这其中&#xff0c;虚拟机网络是绝对绕不过去的。本篇文章通俗易懂的介绍了常见的虚拟网络提供的三种网络链接模…

数据结构-3.5.队列的顺序实现

一.队列的顺序实现&#xff0c;初始化操作以及判断队列是否为空&#xff1a; 1.图解&#xff1a; 2.代码&#xff1a; #include<stdio.h> #define MaxSize 10 //定义一个队列最多存储的元素个数 ​ typedef struct {int data[MaxSize]; //用静态数组存放队列元素int f…

【springboot】整合沙箱支付

目录 1. 配置沙箱应用环境2. 配置springboot项目1. 引入依赖2. 配置文件注册下载ngrok 3. 创建支付宝支付服务类4. 支付界面模板5. 控制类实现支付6. 测试 1. 配置沙箱应用环境 使用支付宝账号登录到开放平台控制台。 使用支付宝登录后&#xff0c;看到以下页面&#xff0c;下…

MFC工控项目实例二十二主界面计数背景颜色改变

承接专栏《MFC工控项目实例二十一型号选择界面删除参数按钮禁用切换》 1、在SEAL_PRESSUREDlg.h文件中添加代码 class CSEAL_PRESSUREDlg : public CDialog { public: CBrush m_brush1;CBrush m_brush2;CBrush m_brush3;... } 2、在SEAL_PRESSUREDlg.cpp文件中添加代码 BO…

在2核2G服务器安装部署MySQL数据库可以稳定运行吗?

阿里云2核2G服务器可以安装MySQL数据库吗&#xff1f;当然可以&#xff0c;并且可以稳定运行MySQL数据库&#xff0c;目前阿里云服务器网aliyunfuwuqi.com使用的就是阿里云2核2G服务器&#xff0c;在云服务器上安装MySQL数据库&#xff0c;可以稳定运行。 目前阿腾云用于运行M…

查看 git log的过程中看到 :说明日志输出可能超出屏幕大小,系统进入了分页模式

在命令行提示符中&#xff0c;通常 : 表示系统等待进一步的输入。如果你在查看 git log 的过程中看到 :&#xff0c;说明日志输出可能超出屏幕大小&#xff0c;系统进入了分页模式&#xff0c;默认使用 less 命令查看内容。 此时你可以&#xff1a; 按 q 退出日志查看。按 En…

算法笔记(五)——分治

文章目录 算法笔记&#xff08;五&#xff09;——分治快排颜色分类排序数组数组中的第K个最大元素库存管理 III 归并排序数组交易逆序对的总数计算右侧小于当前元素的个数翻转对 算法笔记&#xff08;五&#xff09;——分治 分治算法字面上的解释是“分而治之”&#xff0c;就…

Python 从入门到实战32(数据库MySQL)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库编程接口操作的相关知识。今天我们将学习…

【框架篇】过滤器和拦截器的区别以及使用场景

在项目开发中&#xff0c;常常会同时配置拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;&#xff0c;以下就是它们两个主要的区别&#xff1a; 过滤器&#xff08;Filter&#xff09; 配置和实现 Filter的实现还是很简单的&#xff0c;可…

【微服务】组件、基础工程构建(day2)

组件 服务注册和发现 微服务模块中&#xff0c;一般是以集群的方式进行部署的&#xff0c;如果我们调用的时候以硬编码的方式&#xff0c;那么当服务出现问题、服务扩缩容等就需要对代码进行修改&#xff0c;这是非常不好的。所以微服务模块中就出现了服务注册和发现组件&…

计算机毕业设计 基于Python的广东旅游数据分析系统的设计与实现 Python+Django+Vue Python爬虫 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

华为云+WordPress+Puock主题搭建个人博客

网站访问地址&#xff1a;qingxuly.cn 搭建网站 购买华为云服务器&#xff0c;购买域名&#xff0c;进行备案&#xff0c;配置域名解析等操作&#xff0c;请参考华为云文档。 安装Ubuntu系统 华为云控制台中给云服务器安装Ubuntu2204。 配置服务器安全组 华为云安全组中创建安…

【嵌入式系统】第18章 脉宽调试器(PWM)

目录 18.1 结构框图 18.3 功能说明 18.3.4 PWM 信号发生器 18.3.5 死区发生器 18.3.6 中断/ADC 触发选择器 18.3.7 同步方法 18.3.8 故障条件 18.3.9 输出控制块 LES 硬件介绍&#xff08;12&#xff09;正交编码接口QEI 19.1 结构框图 19.2 信号描述 19.3 功能说明…

GPG error golang 1.19

1. 问题描述及原因分析 在飞腾2000的服务器&#xff0c;OS为Kylin Linux Advanced Server release V10环境下&#xff0c;docker版本为18.09.0&#xff08;docker-engine-18.09.0-101.ky10.aarch64&#xff09;&#xff0c;基于容器镜像golang:1.19编译新的容器镜像&#xff0…

C++黑暗迷宫

目录 开头程序程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #include <iostream> #include <cstdlib> #include <ctime> using namespace std; struct near {int i;int ia;int ix;int iy;int iwalk; }; v…

22.1 k8s不同role级别的服务发现

本节重点介绍 : 服务发现的应用3种采集的k8s服务发现role 容器基础资源指标 role :nodek8s服务组件指标 role :endpoint部署在pod中业务埋点指标 role :pod 服务发现的应用 所有组件将自身指标暴露在各自的服务端口上&#xff0c;prometheus通过pull过来拉取指标但是promet…

SQL中基本SELECT语句及常见关键字的使用(内连接,左/右连接)

这里写目录标题 SQL中基本SELECT语句的使用SQL语法简介DDL、DML、DCLSEECT SELECT常用关键词group by分组having筛选limit限定条数UION和UION ALL合并SQL执行顺序 联表查询多表查询示例特殊用法&#xff1a;笛卡尔积&#xff08;交叉连接&#xff09;等值连接vs非等值连接自连接…

VScode 自定义代码配色方案

vscode是一款高度自定义配置的编辑器, 我们来看看如何使用它自定义配色吧 首先自定义代码配色是什么呢? 看看我的代码界面 简而言之, 就是给你的代码的不同语义(类名, 函数名, 关键字, 变量)等设置不同的颜色, 使得代码的可读性变强. 其实很多主题已经给出了定制好的配色方案…

D3.js中国地图可视化

1、项目介绍 该项目来自Github&#xff0c;基于D3.js中国地图可视化。 D3.js is a JavaScript library for manipulating documents based on data. It uses HTML, SVG, and CSS to display data. The full name of D3 is "Data-Driven Documents," which means it a…