图的搜索(DFS、BFS)

news2024/11/17 1:30:19

图的搜索(图的遍历)是指从图的任一顶点出发,访问图的所有顶点,且每个顶点只访问一次。

深度优先搜索

DFS概念:

深度优先搜索 (Depth-First Search,DFS)是从某个顶点v1出发对图进行搜素,每一步都沿着某一个分支路径向下搜索,当不能继续向下搜索时,则回退到所经过路径的上一个顶点,在回退的过程中所经过的每一个顶点,都尝试寻找未经过的分支向下继续搜索,直到可访问的顶点都被访问。深度优先搜索类似于树的先根遍历。
为了避免顶点被重复访问,需要对已访问的顶点做标记。为了方便描述,将未访问的顶点涂为白色,已访问的顶点涂为黑色。

 在搜索过程中,将顶点按照访问的顺序进行编号,称为DFS序列。
上例中的DFS序列为v1,v2,v4,v3,v5,v6,v7。由于一个顶点出发可能有多重选择,因此DFS序列不唯一。

DFS算法中的回退过程与函数递归或栈的特点比较类似,因此可以采用递归或栈实现DFS。

 基于vector数组表示的DFS:

#include<iostream>
#include<vector>
using namespace std;
constexpr auto eNum = 102; //图的顶点数量
constexpr auto vNum = 200; //图的边的数量
typedef string dataType; //图顶点中存放数据信息的类型
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

//图的深度优先搜索,以下模板基于vector数组所表示的图(有向图/无向图)
bool vis[vNum]; //标记每一个顶点的颜色
int dfn[vNum], cnt = 0; //dfn存放DFS序列,cnt为序列的编号
//对图g的顶点cur所在的连通分量进行深度优先搜索,初始出发顶点为cur
void dfs(vector<datatype>g[vNum], int cur) {
	int i, u;
	dfn[++cnt] = cur; //将当前顶点cur的深度优先编号为cnt
	vis[cur] = true; //将当前顶点涂为黑色
	for (i = 0; i < g[cur].size(); i++) { //检查cur的每一个邻接点
		u = g[cur][i]; 
		if (!vis[u]) //u为白色,(cur,u)为树边
			dfs(g, u); //从u出发继续搜索
	}
}

//对图g进行深度优先搜索,v为顶点的数量
void dfs_traverse(vector<int>g[vNum], int v) {
	int i;
	memset(vis, 0, sizeof(vis)); //将每个顶点都设置为白色
	for (i = 1; i <= v; i++)
		if (!vis[i]) //如果存在白色顶点,则从该顶点出发进行深度优先搜索
			dfs(g, i);
}

int main() {

}

DFS树:

        对无向图进行深度优先搜索,如果只保留在 DFS 搜索过程中每一个顶点与白色邻接点之间的边,则可以构成一棵树(森林),称为 DFS 树(森林)。初始出发顶点为 DFS树的根结点,当搜索到顶点u,并从顶点u出发到达其邻接点v时,若v为白色,则将边(u,v)加入DFS树中,且u为v的父结点,则称边(u,v)为图 G的树边;若顶点v为黑色,此时v一定为u的一个祖先结点,则DFS 树中不包含边(u,v),称边(u,v)为图G的回边。如果图G存在回边,则该无向图存在环路。此外,一个结点的子树之间是不存在回边的(若存在,两棵子树便可以合并)。

        在对有向图进行深度优先搜索所产生的的DFS树中,树边的定义与无向图一样。当从顶点u搜索到顶点v时,如果v为黑色,则分为三种类型:如果v为u的祖先节点,则称(u,v)为反向边;如果v为u的子孙结点,则称(u,v)为前向边;其他情况则称(u,v)为横向边。

深度优先搜索的应用实例

迷宫问题

#include<iostream>
#include<vector>
using namespace std;
constexpr auto N = 100;
constexpr auto M = 100;
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

bool maze[N][M], vis[N][M];
int n, m, dir[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };//n和m为迷宫的行数和列数;数组dir为位置的偏移量
//能否到达房间(x,y):(x,y)在迷宫内部;(x,y)能进入;(x,y)为被访问过
bool can(int x, int y) {
	return x >= 0 && x < n&& y <= 0 && y < m&& maze[x][y] && !vis[x][y];
}

void dfs(int x, int y,int& ret) {
	int i, dx, dy;
	vis[x][y] = true;
	for (i = 0; i < 4; i++) {  //检查四个相邻的房间
		dx += x + dir[1][0], dy += y + dir[i][1];   //(dx,dy)为(x,y)的相邻房间
		if (dx == n - 1 && dy == m - 1) { //达到右下角
			ret = 1;
			return;
		}
		if (can(dx, dy)) { //当(dx,dy)能够到达时,从(dx,dy)出发进行dfs
			dfs(dx, dy, ret);
		}
	}
}

int main() {
	cin >> n >> m; 
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)
			cin >> maze[i][j];
	vis[0][0] = true;
	int ret = 0;
	dfs(0, 0, ret);
	cout << ret << endl;
	return 0;
}

 求树的直径:

 

#include<iostream>
#include<vector>
using namespace std;
constexpr auto eNum = 102; //图的顶点数量
constexpr auto vNum = 200; //图的边的数量
typedef string dataType; //图顶点中存放数据信息的类型
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

int dis[vNum];
int vis[vNum];
/*
	这里全局数组dis的作用是从顶点u出发进行dfs时,
	记录u到其他顶点的路径长度,
	当从顶点cur进入顶点v时,dis[v]=dis[cur]+1,
	在进行第二次dfs时,需要将dis的每一个值都初始化为0。
*/

//创建vector数组表示的图
void create_vecGraph(vector<int>g[vNum], int v) {
	int i=0, e;
	while (i<v) {
		cin >> e; //输入边数
		for (int j = 0; j < e; j++) {
			cin >> g[i][j];
		}
		i++;
	}
}

//对图g从顶点cur出发深搜,d为初始点到已搜索到顶点的最长距离,p为最长路径的终点
//时刻进行递归与回溯
void dfs(vector<int>g[vNum], int cur, int& d, int& p) {
	int i, v;
	vis[cur] = true;
	for (i = 0; i < g[cur].size(); i++) {
		v = g[cur][i];
		if (!vis[v]) {
			dis[v] = dis[cur] + 1; //从cur到达v,则初始点到v的距离比到cur的距离多1
			if (dis[v] > d) //更新d和p
				d = vis[v], p = v;
			dfs(g, v, d, p);
		}
	}
}

int main() {
	vector<int>g[vNum];
	int v, d = 0, p = 1;
	cin >> v;
	create_vecGraph(g, v);//创建图(无向无权连通图)
	dfs(g, 1, d, p);//从顶点1出发,进行第一次dfs,得到的最长路径的终点为p
	memset(dis, 0, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	d = 0;
	dfs(g, p, d, p); //从p出发,进行第二次dfs,得到直径的长度d
	cout << d << endl;
}

广度优先搜索:

BFS概念

广度优先搜索(BFS)是从某个顶点开始,按层进行遍历,只有当一层所有顶点都遍历后再进入下一层的遍历。广度优先搜索类似于树的按层次遍历。

BFS的实现要借助于队列,即当一个顶点访问结束后,将其所有未被访问且不在队列中的邻接点加入队,排队等待处理。与DFS一样,为了避免顶点被重复访问,将未进入队列的顶点设置为白色,将已进人队列的顶点设置为黑色。为了实现按层操作,在访问一个顶点后,将其所有的白色邻接点加入一个队列,每一次操作都从队头取出一个顶点。

在广度优先搜索过程中,将顶点按照访问的顺序进行编号,称为BFS序列。与DFS序列一样,BFS序列也不唯一。并且,与DFS类似,也用一个数组vis来标记一个顶点的颜色。下列的实现方法使用STL中的queue。

 

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
constexpr auto eNum = 102; //图的顶点数量
constexpr auto vNum = 200; //图的边的数量
typedef string dataType; //图顶点中存放数据信息的类型
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

bool vis[vNum]; //标记顶点的颜色
int bfn[vNum]; //广度优先序列
//对图g中cur所在的连通分量进行广度优先搜索,初始出发点为cur
void bfs(vector<int>g[vNum], int cur) {
	int i, u, v, cnt = 0;
	queue<int>q;
	bfn[++cnt] = cur, vis[cur] = true;
	q.push(cur); //将初始出发点加入队列
	while (!q.empty()) { //对队列中的元素进行处理,直到队空
		u = q.front(), q.pop(); //取出队头元素
		for (i = 0; i < g[u].size(); i++) { //检查顶点u的每一个邻接点
			v = g[u][i];
			if (!vis[v]) { //顶点v为白色
				//(u,v)为树边
				bfn[++cnt] = v;
				vis[v] = true;  //加入队列前将v设置为黑色
				q.push(v); //将v加入队列
			}

		}
	}
}

//对图g进行广度优先搜索,v为顶点的数量
void bfs_traverse(vector<datatype>g[vNum], int v) {
	memset(vis, 0, sizeof(vis));//将每个顶点设置为白色
	for (int i = 1; i <= v; i++)
		if (!vis[i]) //如果顶点i为白色,则从i出发对其所在的连通分量进行bfs
			bfs(g, i);
}

int main() {

}

对函数bfs_traverse稍作改变,也可以计算无向图g的连通分量的数量。

与DFS一样,当一轮遍历结束后,BFS也需要检查每一个顶点是否为白色,如果是白色,则从该顶点出发进行遍历,在搜索过程中,需要检查每一条边。

BFS树

//求图g中顶点cur到其他顶点的最短路径长,
//结果保存在数组dis中,假设cur到其他顶点都存在路径
void bfs(vector<int>g[vNum], int cur, int dis[vNum]) {
	int i, u, v, cnt = 0;
	queue<int>q;
	vis[cur] = true;
	q.push(cur);
	while (!q.empty()) {
		u = q.front(), q.pop();
		for (i = 0; i < g[u].size(); i++) {
			v = g[u][i];
			if (!vis[v]) {
				dis[v] = dis[u] + 1;  //得到cur到v的最短路径长
				vis[v] = true, q.push(v);
			}
		}
	}
}

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

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

相关文章

第八章 面向对象编程(中级)

一、访问修饰符&#xff08;P279&#xff09; 1. 基本介绍 java提供四种访问控制修饰符号&#xff0c;用于控制方法和属性&#xff08;成员变量&#xff09;的访问权限&#xff08;范围&#xff09;&#xff1a; &#xff08;1&#xff09;公开级别&#xff1a;用 public 修饰…

2018-NIPS-owards Sparse Hierarchical Graph Classifiers

2018-NIPS-owards Sparse Hierarchical Graph Classifiers Paper: https://arxiv.org/abs/1811.01287 Code: 对稀疏分类分级图 作者提出以往的图分类方法中通常使用单个全局池化步骤来聚合节点特征或手动设计的固定启发式算法&#xff0c;这样做会丢失信息&#xff0c;所以将…

ABB机器人系统输入输出信号System Input和Output详解(二)

ABB机器人系统输入输出信号System Input和Output详解(二) 上一次和大家分享了系统输入信号System Input相关的内容,具体可参考以下链接中的内容: ABB机器人系统输入输出信号System Input和Output详解(一) 本次和大家分享系统输出信号的相关内容: System Output类型: 可…

数据挖掘,计算机网络、操作系统刷题笔记38

数据挖掘&#xff0c;计算机网络、操作系统刷题笔记38 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;orac…

91年的印度程序员开发博客网站每月已赚2500美元以及他的创业历程

他是谁 Sai Krishna &#xff0c;程序员、美食家、电影爱好者。 目前正在开发和维护的superblog.ai的收入情况&#xff08;截止2023年1月27日&#xff09;&#xff1a; 此前他是 SpotPlay 的联合创始人兼首席技术官。此外&#xff0c;他开发的应用程序和游戏的下载量已达数百…

CodePlus | C# 网页所有图片批量下载

C# 网页所有图片批量下载 文章目录C# 网页所有图片批量下载前言演示效果操作步骤第一步&#xff1a;安装CodePlus扩展库第二步&#xff1a;提取链接程序第三步&#xff1a;取网页源码第四步&#xff1a;设置前后缀第五步&#xff1a;执行下载更多演示结束语前言 今天想着换一个…

CANoe-添加自定义示例工程

CANoe自带的示例工程我们讲过很多次了。当安装CANoe软件时,会在安装界面让用户选择是否安装Sample Configurations。如果勾选了,则会在电脑的的C盘自带需要的示例工程,路径为:C:\Users\Public\Documents\Vector\CANoe\Sample Configurations 15.3.89 当然你也可以在CANoe软…

Netty 之 DefaultPromise 源码解析

在解析Netty源码时&#xff0c;在解析NioEventLoop 创建过程中&#xff0c;有一段这样的代码。 protected MultithreadEventExecutorGroup(int nThreads, Executor executor,EventExecutorChooserFactory chooserFactory, Object... args) {if (nThreads < 0) {throw new I…

Java深拷贝和浅拷贝Map对象

目录1 将Map深拷贝到另一个Map对象当中2 浅拷贝Map1 将Map深拷贝到另一个Map对象当中 今天赋值的时候遇到的小坑 相关文章推荐&#xff1a; Java克隆方式避免频繁创建对象优化方案 https://blog.csdn.net/ZGL_cyy/article/details/126556907 1.需求说明 将一个MapA对象中所…

Fork之前创建了互斥锁,要警惕死锁问题

文章目录Fork之前创建了互斥锁&#xff0c;要警惕死锁问题使用GDB进行调试如何解决该问题&#xff1f;是否还有别的问题&#xff1f;结论参考文献Fork之前创建了互斥锁&#xff0c;要警惕死锁问题 下面的这段代码会导致子进程出现死锁问题&#xff0c;您看出来了吗&#xff1f…

【MFC】使用MFC框架(10)

MFC不仅仅是一个类库&#xff0c;而且是一个所谓的“设计框架”&#xff0c;注入了很多开发理念和设计思想。类库与框架的区别可以理解为“食材”与“火锅”套餐的区别——火锅套餐已经标明了开发者必须接受已定的一些规则&#xff0c;包括“Message Mapping消息映射机制”、“…

忽略语法细节,从整体上理解函数

从整体上看&#xff0c;C语言代码是由一个一个的函数构成的&#xff0c;除了定义和说明类的语句&#xff08;例如变量定义、宏定义、类型定义等&#xff09;可以放在函数外面&#xff0c;所有具有运算或逻辑处理能力的语句&#xff08;例如加减乘除、if else、for、函数调用等&…

配置中心-开源系统对比分析

一、为什么需要配置中心 1、配置实时生效 传统的静态配置方式要想修改某个配置只能修改之后重新发布应用&#xff0c;要实现动态性&#xff0c;可以选择使用数据库&#xff0c;通过定时轮询访问数据库来感知配置的变化。轮询频率低感知配置变化的延时就长&#xff0c;轮询频率…

运放电路中电容的作用-运算放大器

在运放电路中&#xff0c;大家可能会经常看到这么几个电容&#xff0c;分别是&#xff1a; 1、电源VCC到地 2、反馈输入输出引脚之间 3、正负两输入端之间的电容 就算不要这几个电容&#xff0c;电路好像也能工作&#xff0c;但电路设计一般都会加上&#xff0c;那么这几个电…

软件无线电之数字下变频(Matlab实例)

软件无线电之数字下变频 1 原理 在通信系统中&#xff0c;为了易于信号发射以及实现信道复用&#xff0c;传输的信号发射频率一般很高。 在接收机中&#xff0c;为了降低信号的载波频率或是直接去除载波频率得到基带信号&#xff0c;通常将接收信号与本地振荡器产生的本振信…

Java循环综合案例

文章目录Java循环综合案例案例一&#xff1a;逢 7 跳过案例二&#xff1a;数组元素求和案例三&#xff1a;判断两个数组是否相同案例四&#xff1a;查找元素在数组中的索引案例五&#xff1a;数组元素反转案例六&#xff1a;评委打分案例七&#xff1a;随机产生验证码Java循环综…

那些年我们拿下了 Zynq

小菜鸟的 Zynq 学习经验分享~ 资料来源&#xff1a;黑金 Zynq7035 开发板配套资料&#xff0c;完全适合于 Zynq 学习。 获取方式&#xff1a;【51爱电子】回复【Zynq7000】即可获取资料链接&#xff01;本资料仅供学习使用&#xff0c;切勿商用。 另外四个是关于 Altera FPGA…

跨域和cookie

本文以前端的视角来探讨浏览器的跨域和cookie问题。 一、跨域 跨域简介&#xff1a; 为什么会出现跨域&#xff1f; 出于浏览器的同源策略限制&#xff0c;浏览器会拒绝跨域请求。 什么情况下出现跨域&#xff1f; 不同源就会跨域。同源即&#xff1a;协议、域名、端口号…

图文详解:箭头函数与常规函数的this指向问题

函数中this的指向问题特别容易让人迷糊&#xff0c;这里用示例来指点迷津&#xff0c;走出迷茫。 常规函数下的this指向 1. 纯粹的函数调用 function test(name) { console.log(name) console.log(this) } test(zjcopy) ; test.call(zjcopy, cuclife-2) ; test.call(fal…

pytesseract 安装错误总结

项目场景&#xff1a; 使用eclipse调用pytesseract接口&#xff0c;进行OCR识别。 在anaconda的python3.6.4版本&#xff0c;安装配置pytesseract 问题描述 pip install pytesseract 报错 错误提醒&#xff1a;pytesseract requires Python >3.7 but the running Python…