【数据结构】六、图:4.图的遍历(深度优先算法DFS、广度优先算法BFS)

news2024/12/26 21:51:45

三、基本操作

文章目录

  • 三、基本操作
    • 1.图的遍历
      • 1.1 深度优先遍历DFS
        • 1.1.1 DFS算法
        • 1.1.2 DFS算法的性能分析
        • 1.1.3 深度优先的生成树和生成森林
      • 1.2 广度优先遍历BFS
        • 1.2.1 BFS算法
        • 1.2.2 BFS算法性能分析
        • 1.2.3 广度优先的生成树和生成森林
      • 1.3 图的遍历与图的连通性

1.图的遍历

图的遍历是和树的遍历类似,我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次, 这一过程就叫做图的遍历(Traversing Graph)。

对于图的遍历来,通常有两种遍历次序方案:

  1. 深度优先遍历
  2. 广度优先遍历

1.1 深度优先遍历DFS

深度优先遍历(Depth First Search),也有称为深度优先搜索,简称为DFS。

1.1.1 DFS算法

深度优先搜索类似于树的先序遍历。如其名称中所暗含的意思一样,这种搜索算法所遵循的搜索策略是尽可能“深”地搜索一个图,每次都尝试向更深的节点走。它的基本思想如下:

首先访问图中某一起始顶点v,然后由v出发,访问与v邻接且未被访问的任一顶点w1,再访问与 w1 邻接且未被访问的任一顶点…重复上述过程。当不能再继续向下访问时,依次退回到最近被访问的顶点,若它还有邻接顶点未被访问过,则从该点开始继续上述搜索过程,直至图中所有顶点均被访问过为止。

在这里插入图片描述

先序遍历:12563478

DFS 最显著的特征在于其 递归调用自身。同时与 BFS 类似,DFS 会对其访问过的点打上访问标记,在遍历图时跳过已打过标记的点,以确保 每个点仅访问一次。符合以上两条规则的函数,便是广义上的 DFS。算法过程如下:

bool visited[MAX_VERTEX_NUM];	//访问标记数组

//从顶点出发,深度优先遍历图G
void DFS(Graph G, int v){
	visit(v);	//访问顶点
	visited[v] = TRUE;	//设已访问标记
	//FirstNeighbor(G,v):求图G中顶点v的第一个邻接点,若有则返回顶点号,否则返回-1。
	//NextNeighbor(G,v,w):假设图G中顶点w是顶点v的一个邻接点,返回除w外顶点v
	for(int w = FirstNeighbor(G, v); w>=0; w=NextNeighor(G, v, w)){
		if(!visited[w]){	//w为v的尚未访问的邻接顶点
			DFS(G, w);//递归
		}
	}
}

但是如果是非连通图,那么就不能从一个结点遍历所有的结点。这个时候需要添加一个函数来寻找没被访问的结点。

//深度遍历算法final
bool visited[MAX_VERTEX_NUM];	//访问标记数组

//从顶点出发,深度优先遍历图G
void DFS(Graph G, int v){
	visit(v);	//访问顶点
	visited[v] = TRUE;	//设已访问标记
	//FirstNeighbor(G,v):求图G中顶点v的第一个邻接点,若有则返回顶点号,否则返回-1。
	//NextNeighbor(G,v,w):假设图G中顶点w是顶点v的一个邻接点,返回除w外顶点v
	for (int w = FirstNeighbor(G, v); w>=0; w=NextNeighor(G, v, w)){
		if (!visited[w]){	//w为v的尚未访问的邻接顶点
			DFS(G, w);//递归
		}
	}
}

//对图进行深度优先遍历
void DFSTraverse(MGraph G){
	for (int v=0; v<G.vexnum; v++){
		visited[v] = FALSE;	//初始化已访问标记数据
	}
	for (int v=0; v<G.vexnum; v++){	//从v=0开始遍历
		if(!visited[v]){
			DFS(G, v);
		}
	}
}
1.1.2 DFS算法的性能分析

空间复杂度:DFS算法是一个递归算法,需要借助一个递归工作栈。最坏情况是 O(|V|)

在这里插入图片描述

时间复杂度:

  • 邻接矩阵:需要访问|V|个结点,然后查找|V|个结点的邻接点|V|个,那么时间复杂度为 O(|V|+|V|2)。
    O(|V|2)
  • 邻接表:需要访问|V|个结点,然后查找每个结点的邻接点总共需要O(2|E|)时间,那么时间复杂度为 O(|V|+2|E|)。
    O(|V|+|E|)
1.1.3 深度优先的生成树和生成森林

对一个图进行所有结点的遍历,那么在这个遍历过程中不是所有的边都被用到:

在这里插入图片描述

**【注意】**1. 从不同的点出发,得到的遍历序列不一样;即使从同一个点出发,也可能得到不同的遍历序列。

  1. 因为邻接矩阵的表示方式是唯一的,所以DFS算法得到的遍历序列是唯一的。但是因为单链表的表示方式不是唯一的,所以DFS算法得到的遍历序列不是唯一的。

当图里面有多个连通分量,那么就会有多个生成树,这时候这些树就组成一个生成森林。

在这里插入图片描述

1.2 广度优先遍历BFS

广度优先遍历(Breadth First Search),又称为广度优先搜索,简称BFS。

1.2.1 BFS算法

如果说图的深度优先遍历类似树的前序遍历,那么图的广度优先遍历就类似于树的层序遍历了。

广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有往回退的情况,因此它不是一个递归的算法。为了实现逐层的访问,算法必须借助一个辅助队列,以记忆正在访问的顶点的下一层顶点。

在这里插入图片描述

以下是广度优先遍历的代码:

从结点v出发遍历:

//邻接矩阵的广度遍历算法。从结点v出发遍历
void BFS(MGraph G, int v){
	Queue Q;
    //把所有结点全部标记为false,表示没有访问过
	for(int i = 0; i<G.numVertexes; i++){
		visited[i] = FALSE;
	}
	InitQueue(&Q);	//初始化一辅助用的队列
    
    visit(v);	//访问顶点
	vivited[v] = TRUE;	//设置当前访问过
    EnQueue(&Q, v);	//将此顶点入队列
            
    //若当前队列不为空
    while(!QueueEmpty(Q)){
        DeQueue(&Q, &v);	//顶点v出队列

        //FirstNeighbor(G,v):求图G中顶点v的第一个邻接点,若有则返回顶点号,否则返回-1。
        //NextNeighbor(G,v,w):假设图G中顶点w是顶点v的一个邻接点,返回除w外顶点v

        //把出队结点的相邻的所有结点入队
        for(int w=FirstNeighbor(G, v); w>=0; w=NextNeighbor(G, v, w)){
            //检验v的所有邻接点
            if(!visited[w]){
                visit(w);	//访问顶点w
                visited[w] = TRUE;	//访问标记
                EnQueue(&Q, w);	//顶点w入队列
            }//if
        }//for
    }//while
}

但是如果是非连通图,那么就不能从一个结点遍历所有的结点。这个时候需要添加一个函数来寻找没被访问的结点。

//邻接矩阵的广度遍历算法final
void BFSTraverse(MGraph G){
	int i, j;
	Queue Q;
    //把所有结点全部标记为false,表示没有访问过
	for(i = 0; i<G.numVertexes; i++){
		visited[i] = FALSE;
	}
	InitQueue(&Q);	//初始化一辅助用的队列
    
	for(i=0; i<G.numVertexes; i++){		//这里是从0开始
		//若是未访问过就处理
		if(!visited[i]){
            
        //下面的部分相当于前面写的BFS(G, v);
            
			visit(i);	//访问顶点
            vivited[i] = TRUE;	//设置当前访问过
			EnQueue(&Q, i);	//将此顶点入队列
            
			//若当前队列不为空
			while(!QueueEmpty(Q)){
				DeQueue(&Q, &i);	//顶点i出队列
                
				//FirstNeighbor(G,v):求图G中顶点v的第一个邻接点,若有则返回顶点号,否则返回-1。
				//NextNeighbor(G,v,w):假设图G中顶点w是顶点v的一个邻接点,返回除w外顶点v
                
                //把出队结点的相邻的所有结点入队
				for(j=FirstNeighbor(G, i); j>=0; j=NextNeighbor(G, i, j)){
					//检验v的所有邻接点
					if(!visited[j]){
						visit(j);	//访问顶点j
						visited[j] = TRUE;	//访问标记
						EnQueue(Q, j);	//顶点j入队列
					}//if
				}//for
			}//while
            
		}//if
	}//for
}

下面的部分相当于前面写的BFS(G, v);,所以还有一种写法是把BFSTraverse 和 BFS分开

//对非连通图的广度遍历算法final
void BFSTraverse(MGraph G){
    Queue Q;
    InitQueue(&Q);	//初始化一辅助用的队列
    
	int i;
    //把所有结点全部标记为false,表示没有访问过
	for(i=0; i<G.numVertexes; i++){
		visited[i] = FALSE;
	}
	
    
	for(i=0; i<G.numVertexes; i++){		//这里是从0开始
		//若是未访问过就处理
		if(!visited[i]){
			BFS(G, i);	//调用BFS函数
        }//if
	}//for
}

void BFS(MGraph G, int v){
    visit(v);	//访问顶点
    vivited[v] = TRUE;	//设置当前访问过
    EnQueue(&Q, v);	//将此顶点入队列

    //若当前队列不为空
    while(!QueueEmpty(Q)){
        DeQueue(&Q, &v);	//顶点i出队列

        //FirstNeighbor(G,v):求图G中顶点v的第一个邻接点,若有则返回顶点号,否则返回-1。
        //NextNeighbor(G,v,w):假设图G中顶点w是顶点v的一个邻接点,返回除w外顶点v

        //把出队结点的相邻的所有结点入队
        for(w=FirstNeighbor(G, v); w>=0; w=NextNeighbor(G, v, w)){
            //检验v的所有邻接点
            if(!visited[w]){
                visit(w);	//访问顶点w
                visited[w] = TRUE;	//访问标记
                EnQueue(Q, w);	//顶点w入队列
            }//if
        }//for
    }//while
}

对于无向图,调用BFS函数的次数 = 连通分量

1.2.2 BFS算法性能分析

空间复杂度:最坏情况是当所有结点都连在第一个结点上,辅助队列大小为 O(|V|)

时间复杂度:

  • 邻接矩阵:需要访问|V|个结点,然后查找|V|个结点的邻接点|V|个,那么时间复杂度为 O(|V|+|V|2)。
    O(|V|2)
  • 邻接表:需要访问|V|个结点,然后查找每个结点的邻接点总共需要O(2|E|)时间,那么时间复杂度为 O(|V|+2|E|)。
    O(|V|+|E|)
1.2.3 广度优先的生成树和生成森林

对一个图进行所有结点的遍历,那么在这个遍历过程中不是所有的边都被用到:

在这里插入图片描述

【注意】因为邻接矩阵的表示方式是唯一的,所以BFS算法得到的遍历序列是唯一的。但是因为单链表的表示方式不是唯一的,所以BFS算法得到的遍历序列不是唯一的。

在这里插入图片描述

当图里面有多个连通分量,那么就会有多个生成树,这时候这些树就组成一个生成森林。

1.3 图的遍历与图的连通性

图的遍历算法可以用来判断图的连通性。

  • 对于无向图进行BFS/DFS遍历。

    调用BFS/DFS函数的次数 = 连通分量数。

    • 若是连通图的,则从任一结点出发, 仅需一次遍历就能够访问图中的所有顶点,只需调用1次BFS/DFS。
    • 若是非连通的,则从某一个顶点出发,一次遍历只能访问到该顶点所在连通分量的所有顶点,而对于图中其他连通分量的顶点,则无法通过这次遍历访问。
  • 对于有向图进行BFS/DFS遍历。
    调用BFS/DFS函数的次数要具体问题具体分析

    • 若起始顶点到其他各顶点都有路径,则只需调用1次BFS/DFS函数,对于强连通图,从任一结点出发都只需调用1次BFS/DFS。
    • 但是从起始顶点不能到达所有结点,那么需要调用多次BFS/DFS。

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

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

相关文章

synergy A problem occurred during installation, try installing.

系统&#xff1a;macos m2 解决方式&#xff1a; 在 ~/Library/LaunchAgents/ 目录下, 新建 com.symless.synergy3.plist 文件&#xff0c;并chown为当前用户。初始化成功

Latex或者word里面mathtype类型的数学公式如何变成mathematica里面的形式

详细步骤如下&#xff1a; 第一步&#xff1a;Latex里面的公式复制粘贴到word里面&#xff0c;转变成mathtype类型的数学公式&#xff08;若已经是word里面mathtype类型的数学公式&#xff0c;这一步可以省略&#xff09;&#xff0c;如下&#xff1a; 第二步&#xff1a;将ma…

探索SD NAND配套测试工具:工程师的得力助手

在快速发展的存储技术领域&#xff0c;SD NAND因其高速读写、低功耗和高可靠性而广受青睐。然而&#xff0c;对于工程师来说&#xff0c;验证SD NAND的性能并非易事&#xff0c;为了便于工程师验证&#xff0c;MK 米客方德开发设计了SD NAND配套测试工具。 一、SD NAND转接板简…

深度学习-----------------多个输入和输出通道

目录 多个输入通道多个输出通道多个输入和输出通道11卷积层二维卷积层总结多输入多输出通道代码实现多输入单输出通道代码实现多输出通道代码实现该部分代码 多输入多输出通道总代码多个输入和输出通道用途 11卷积该部分总代码 问题 多个输入通道 彩色图像可能有RGB三个通道 转…

【AWS账号解绑关联】Linker账号解绑重新关联注意事项

文章目录 一、来自客户疑问二、提交工单获取帮助三、最佳操作说明四、最佳操作步骤五、参考资料活动上新 一、来自客户疑问 将Linker账号&#xff0c;从一个组织中退出&#xff0c;重新关联到新的组织中&#xff0c;这解绑到重新完成新的关联绑定期间会在Linker账号中的账单中…

Markdown编写及语法

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

Linux 驱动入门(2)—— LED驱动

目录 前言 一、编译替换内核和设备树 二、GPIO子系统 1.引脚编号 2.基于sysfs操作引脚 3.GPIO子系统的函数 三、LED驱动编写 前言 在这里主要记录学习韦东山老师Linux驱动课程的笔记&#xff0c;韦东山老师的驱动课程讲的非常好&#xff0c;想要学习驱动的小伙伴可以去…

mp3转换工具哪个好用?不影响音质的转换器分享

暑假里&#xff0c;#大学生暑期生活日常#总是充满活力&#xff0c;有的同学会选择通过音乐来放松心情&#xff0c;享受生活。 但有时候&#xff0c;我们下载的音乐文件格式并不总是我们想要的&#xff0c;这时候使用mp3格式转换器在线转换音频就是最好的选择。 接下来&#x…

【使用教程】CiA402中的“原点回归模式”和“轮廓位置模式”搭配使用操作实例

使用“原点回归模式”配合“轮廓位置模式”是步进或伺服电机使用过程中最常用的方法&#xff0c;其对于提高自动化生产线的准确性和效率具有重要意义&#xff0c;本文将对正常使用控制电机中发送的命令及顺序进行简要说明。 说明&#xff1a;“原点回归”以“堵转回原点”的方式…

RT-DETR中的CCFF结构代码详解(Pytorch)

代码链接 lyuwenyu/RT-DETR: [CVPR 2024] Official RT-DETR (RTDETR paddle pytorch), Real-Time DEtection TRansformer, DETRs Beat YOLOs on Real-time Object Detection. &#x1f525; &#x1f525; &#x1f525; (github.com)https://github.com/lyuwenyu/RT-DETR 模…

计算机网络408考研 2015

计算机网络408考研2015年真题解析_哔哩哔哩_bilibili 1 1线路编码(NRZ,NRZI,8B/10B,Manchester)与加扰_nrz编码-CSDN博客 1 1 11

sunspec协议储能电能计量装置

电网公司通常要求光伏并网系统为不可逆流发电系统&#xff0c;即光伏并网系统所发的电由本地负荷消耗&#xff0c;多余的电不允许通过低压配电变压器向上级电网逆向送电。在并网发电系统中&#xff0c;由于外部环境是不断变化的&#xff0c;为了防止光伏并网系统逆向发电&#…

DLL修复工具免费版本推荐:有效修复DLL文件问题

在Windows系统中&#xff0c;DLL&#xff08;动态链接库&#xff09;文件扮演着至关重要的角色。它们为多个程序共享代码和资源&#xff0c;节省内存并促进程序之间的高效运行。然而&#xff0c;DLL文件的损坏或丢失可能导致各种问题&#xff0c;如程序崩溃、系统不稳定甚至蓝屏…

大数据技术——实战项目:广告数仓(第五部分)

目录 第9章 广告数仓DIM层 9.1 广告信息维度表 9.2 平台信息维度表 9.3 数据装载脚本 第10章 广告数仓DWD层 10.1 广告事件事实表 10.1.1 建表语句 10.1.2 数据装载 10.1.2.1 初步解析日志 10.1.2.2 解析IP和UA 10.1.2.3 标注无效流量 10.2 数据装载脚本 第9章 广…

Ubuntu中设置环境变量 PATH 的命令,不生效的问题“PATH=~/bin:$PATH”

1. 知识点 PATH~/bin:$PATH PATH&#xff1a;这是一个环境变量&#xff0c;用于指定操作系统在哪些目录中查找可执行文件。 ~&#xff1a;这是一个特殊的符号&#xff0c;代表当前用户的主目录。 /bin&#xff1a;这通常是存放标准实用程序&#xff08;如 ls, cp 等&#xff…

解决Openwrt 串口默认是没有密码的方法

将串口登录加入密码方法如下&#xff1a; 步骤一&#xff1a;配置busybox的登录&#xff0c;可以在.config文件中添加如下 CONFIG_BUSYBOX_CONFIG_LOGINy 添加后&#xff0c;需要重新编译busybox。 步骤二&#xff1a;修改target/linux/ramips/base-files/etc/inittab文件 将…

C++之类与对象(中)(上篇)

类与对象&#xff08;中&#xff09; 类的默认成员函数 默认成员函数就是⽤⼾没有显式实现&#xff0c;编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类&#xff0c;我 们不写的情况下编译器会默认⽣成以下6个默认成员函数&#xff0c;需要注意的是这6个中最重要的是前4…

ECCV 2024 | 南洋理工三维数字人生成新范式:结构扩散模型

该论文作者均来自于新加坡南洋理工大学 S-Lab 团队&#xff0c;包括博士后胡涛&#xff0c;博士生洪方舟&#xff0c;以及计算与数据学院刘子纬教授&#xff08;《麻省理工科技评论》亚太地区 35 岁以下创新者&#xff09;。S-Lab 近年来在顶级会议如 CVPR, ICCV, ECCV, NeurIP…

ICE.AI战略扩展亚太市场,创新交易模式及平台全面升级

2024年8月11日,纽约——全球金融科技领军企业,Intercontinental Exchange Inc.宣布,公司将加速在亚太市场的战略扩展,并通过进一步优化交易模式和平台功能,巩固其在全球市场的卓越地位,同时积极探索新的获利机会。 ICE.AI自推行以来,凭借前沿的人工智能技术和深度学习算法,为全…

shell编程:利用SSH实现分布式应用的一键安装部署②(脚本安装java环境、脚本安装配置zookeeper、scala、kafka)

上一节&#xff1a;函数封装 ②脚本安装java环境、脚本安装配置zookeeper、scala、kafka 1 脚本一键部署kafka分布式应用 1.1 脚本安装配置java环境 准备好java安装包&#xff0c;存放到/opt/tmp目录下。我这里使用的是jdk-8u212-linux-x64.tar.gz&#xff0c;在网上找对应…