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

news2024/11/28 10:50:30

在这里插入图片描述

文章目录

  • 遍历算法
    • 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.coloradmin.cn/o/2186773.html

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

相关文章

【算法笔记】双指针算法深度剖析

【算法笔记】双指针算法深度剖析 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;算法笔记 文章目录 【算法笔记】双指针算法深度剖析前言一.移动零1.1题目1.2思路分析1.3代码实现二.复写零2.1题目2.2思路分析2.3代码实现 三.快乐数3.1题目3…

微服务实战——ElasticSearch(保存)

商品上架——ElasticSearch&#xff08;保存&#xff09; 0.商城架构图 1.商品Mapping 分析&#xff1a;商品上架在 es 中是存 sku 还是 spu &#xff1f; 检索的时候输入名字&#xff0c;是需要按照 sku 的 title 进行全文检索的检索使用商品规格&#xff0c;规格是 spu 的…

基于Springboot+Vue的小区停车场管理系统登录(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 在这个…

uniapp 微信发布注意事项

uniapp的微信播放不支持本地文件&#xff0c;起始微信原生语言是支持的 所以在编写uniapp代码时 要写两套逻辑 // #ifdef MP-WEIXIN 微信原封不变的自己写法 //#endif // #ifndef MP-WEIXIN 其他写法 //#endif 这样可实现 发布到微信后 微信原封不动的使用自己写…

初识算法 · 双指针(3)

目录 前言&#xff1a; 和为s的两数之和 题目解析&#xff1a; ​编辑 算法原理&#xff1a; 算法编写&#xff1a; 三数之和 题目解析 算法原理 算法编写 前言&#xff1a; 本文通过介绍和为S的两数之和&#xff0c;以及三数之和&#xff0c;对双指针算法进行深一步…

进度条(倒计时)Linux

\r回车(回到当前行开头) \n换行 行缓冲区概念 什么现象&#xff1f; 什么现象&#xff1f;&#xff1f; 什么现象&#xff1f;&#xff1f;&#xff1f; 自己总结&#xff1a; #pragma once 防止头文件被重复包含 倒计时 在main.c中&#xff0c;windows.h是不可以用的&…

Windows 环境搭建 CUDA 和 cuDNN 详细教程

CUDA CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA公司推出的一个并行计算平台和编程模型&#xff0c;它允许开发者使用NVIDIA GPU进行通用计算&#xff08;即GPGPU&#xff09;&#xff0c;从而加速各种计算密集型任务。CUDA提供了一套基于C/C…

linux文件编程_线程

1. 基本概念 1.1. 进程与线程的概念 典型的UNIX/linux进程可以看成是只有一个控制线程&#xff0c;一个进程在同一时刻只做一件事情&#xff0c;有了多个控制线程后&#xff0c;在程序设计时可以把进程设计成在同一时刻做不止一件事&#xff0c;每个线程各自处理独立的任务。…

Web安全 - 文件上传漏洞(File Upload Vulnerability)

文章目录 OWASP 2023 TOP 10导图定义攻击场景1. 上传恶意脚本2. 目录遍历3. 覆盖现有文件4. 文件上传结合社会工程攻击 防御措施1. 文件类型验证2. 文件名限制3. 文件存储位置4. 文件权限设置5. 文件内容检测6. 访问控制7. 服务器配置 文件类型验证实现Hutool的FileTypeUtil使用…

STM32使用Keil5 在运行过程中不复位进入调试模式

一、选择Options for Target进入设置 二、选择所使用的调试器&#xff0c;这里以ST-Link为例。取消勾选Load Application at Startup 可以在进入调试模式的时候不会从新加载程序&#xff01;从而不破坏现场 三、点击Setting进入 四、取消勾选Reset after Connect 使得调试器连接…

探索 aMQTT:Python中的AI驱动MQTT库

文章目录 探索 aMQTT&#xff1a;Python中的AI驱动MQTT库背景介绍aMQTT是什么&#xff1f;如何安装aMQTT&#xff1f;简单库函数使用方法场景应用常见问题及解决方案总结 探索 aMQTT&#xff1a;Python中的AI驱动MQTT库 背景介绍 在物联网和微服务架构的浪潮中&#xff0c;MQ…

Redis:string类型

Redis&#xff1a;string类型 string命令设置与读取SETGETMSETMGET 数字操作INCRINCRBYDECRDECRBYINCRBYFLOAT 字符串操作APPENDSTRLENGETRANGESETRANGE 内部编码intembstrraw 在Redis中&#xff0c;字符串string存储的是二进制&#xff0c;以byte为单位&#xff0c;输入的二进…

ICPC-day1(NTT)

NTT经典例题 CCPC-Winter-Camp-day6-A——NTT经典例题 对于上面格式&#xff0c;如果想求出每个i的值可以使用卷积求出&#xff0c;因为阶乘j和阶乘i-j相乘的值为(i(i-j))i 补充一个二次剩余定理 P5491 【模板】二次剩余 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) //#in…

【MySQL】DML数据操作语句和基本的DQL语句

目录 一、Mysql对数据的增删改 1. 增加数据 2. 修改数据&#xff08;UPDATE语句&#xff09; 3. 删除 3.1 delete、truncate、drop区别 二、DQL语言&#xff08;重点&#xff09; 1. 单表查询 1.1 最简单的查询 1.2 从表中获取数据 1.3 字段名起别名 1.4 添加字段 1…

[20231103消息] 大模型商业化模式详解:烧钱之后如何挣钱?

距ChatGPT3.5发布已近一年&#xff0c;大模型狂热开始逐步降温&#xff1a;GPU禁运及长期烧钱的事实&#xff0c;让国内的大模型企业&#xff0c;不得不加速商业化考量。 目前&#xff0c;大模型的B端应用已经出现各种定价方法&#xff0c;包括按照时间段收费、按调用量收费以…

class 030 异或运算的骚操作

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。 这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐. https://space.bilibili.com/8888480?spm_id_f…

Java中Map和Set详细介绍,哈希桶的实现

大家好呀&#xff0c;前一节我们接触了二叉搜索树&#xff0c;那么紧接着&#xff0c;我们要学习一种十分重要而且也是我们在初阶数据结构中接触的最后一种数据结构—Map和Set&#xff0c;本篇博客将会详细介绍两种数据结构&#xff0c;并且针对哈希表底层实现一个哈希桶&#…

基于元神操作系统实现NTFS文件操作(三)

1. 背景 本文主要介绍DBR的读取和解析&#xff0c;并提供了基于元神操作系统的实现代码。由于解析DBR的目的是定位到NTFS磁盘分区的元文件$Root进行文件操作&#xff0c;所以只解析了少量的部分&#xff0c;其它部分可以参考相关文档进行理解。 DBR存在于磁盘分区的第一个扇区…

《数据结构》--链表【包含跳表概念】

不知道大家对链表熟悉还是陌生&#xff0c;我们秉着基础不牢&#xff0c;地动山摇的原则&#xff0c;会一点点的介绍链表的&#xff0c;毕竟链表涉及的链式存储也很重要的。在这之前&#xff0c;我们认识过顺序存储的顺序表&#xff0c;它其实就是一个特殊的数组。那链表到底是…