C++深度优先搜索(DFS)的概念(一)

分享内容

  1. 深度优先搜索(DFS)的概念
  2. 深度优先搜索的实现
  3. DFS的应用

互联网的诞生和发展(二)

TCP/IP协议的出现

阿帕网最初使用的是NCP(网络控制程序)协议,但随着网络规模的扩大和计算机类型的多样化,NCP逐渐无法满足需求。1974年:文顿·瑟夫和鲍勃·卡恩提出了TCP/IP协议(传输控制协议/网际协议)。TCP负责将数据分成小的数据包,并确保数据包能够可靠地传输;IP负责将数据包从源地址传输到目标地址。

1983年1月1日:阿帕网正式从NCP协议切换到TCP/IP协议,这一天被认为是现代互联网的诞生日期。TCP/IP协议的成功实施使得不同类型的计算机和网络能够无缝连接,为互联网的快速发展奠定了基础。

##小故事

兔子先生的家对客人来说是个迷宫,他现在要带领客人参观他的家。房间很多,乱走容易迷路或者漏过一些房间,现在看看兔子先生的走法。

1、分叉口,总是先走左边。

2、走到没有路了,回到上一个分叉口:

· 如果右边还没走,就走右边;

· 如果两边都走过了,回上一个分叉口;

继续下去,直到走完所有房间。

先左边,再右边,走过的房间不重复走。

深度优先搜索

这其实也是计算机中常用的搜索方法:

从一个点开始,按照一定的顺序选路,沿着某条路往下走,一直走到底(深入到底),如果走完后发现不能达到目标,返回到上一个点(回溯再试),换条路,然后继续走到底,如此往复,直至所有可能的结果都被搜索完。

这种"深入到底,回溯再试"的方法,称之为深度优先搜索(Deep First Search),简称DFS。

练一练1

根据深度优先搜索思想,下图按照先右后左的顺序从A点开始搜索,画一画搜索的路径。

依次经过的点是ACFEHGBD?(搜索过的点不重复输出)

练一练2

根据深度优先搜索思想,从A点开始搜索下图。请先确定一个搜索顺序_左中右_,再画一画搜索的路径。写出依次经过的点是_ABECD FGH(搜索过的点不重复输出)

迷宫找出口

你站在下面这个迷宫的入口,目标是走出迷宫(只有绿色格子可以通行)。你会怎么做呢?

试着应用DFS(深度优先搜索):"一条路走到黑",直到走不通了再回头换一条路。

  1. 先确定搜索顺序,例如:上下左右
  2. 无法搜索时,返回上一个格子,按搜索顺序换其它方向继续搜索

格子1上下左右依次查看,只能走右,到格子2;

格子2上下左右依次查看,只能走右;

格子3上下左右依次查看,只能走右;

格子4上下左右依次查看,上是墙,按顺序要先走下;

格子6上下左右依次查看,只能走下;

格子9上下左右依次查看,先走下;

成功走出迷宫!

练一练3

假设有一个3行3列共9个格子的迷宫,从(1,2)格子开始,按照下右上左的顺序进行深度优先搜索,最后搜索的格子是?

格子(1,3)下右上左都不能搜索,回溯到上一个格子。

格子(2,3)下右上左也不能搜索,继续回溯。

格子(3,3)下右上左也不能搜索,继续回溯。

到格子(3,2),还有左可以搜索。

所有格子都搜索过了,最后搜索的格子是(1,1)。

搜索路径:(1,2) (2,2) (3,2) (3,3) (2,3) (1,3) (3,1) (2,1) (1,1)

练一练4

地下迷宫很黑。迷宫中的每个房间都有一盏灯,从入口出发,按照下右上左的顺序,点亮所有房间的灯。

从入口房间开始,点亮当前房间的灯。

然后按照下右上左的顺序,选择一个未点亮灯的相邻房间进入并点亮灯。

如果当前房间的所有相邻房间的灯都已点亮,则回退到上一个房间,按照下右上左的顺序寻找未点亮灯的相邻房间。直到逐步点亮所有房间的灯。

深度优先搜索的实现-迷宫遍历

假设有一个3行3列共9个格子的迷宫,从(1,2)格子开始,按照下右上左的顺序进行深度优先搜索,走遍迷宫的9个格子。代码如何实现?

每个格子的搜索方法都是一样的:

如果这个格子之前没有被搜索过,标记这个格子已搜索过,按搜索顺序(下右上左)继续搜索下一个格子。

特别适合用递归来实现。递归将一个大的问题拆解成若干个小的问题,每个小问题的结构都相同。

迷宫遍历

自定义dfs()函数,实现深度优先搜索。

cpp 复制代码
dfs(x,y):深度优先搜索(x,y)格子

void dfs(int x, int y) {
搜索当前格子的下右上左格子

}
cpp 复制代码
创建一个二维数组,false:未搜索,true:已搜索

bool visited [4] [4];//全局变量,所有元素默认初始化为O(false)
cpp 复制代码
主程序:

int main() {
	visited[1][2]= true;标记(1,2)格子
	dfs (1,2);//从(1,2)格子开始深度优先搜索
	return 0;
}

迷宫格子的行列号从1开始,所以visited数组也从下标1开始用。0行0列不用。

自定义dfs()函数,实现深度优先搜索。

dfs(x,y):深度优先搜索(x,y)格子

cpp 复制代码
bool visited [4] [4];
void dfs(int x, int y) {
//搜索当前格子的下右上左格子
	dfs(x+1,y);
	dfs(x,y+1);
	dfs(x-1,y);
	dfs(x,y-1);

//这样就形成了递归

}

但是还需要确保这些格子的坐标在地图内,并且这个格子没有搜索过!

代码简化

添加递归终止条件

cpp 复制代码
bool visited[4][4];//访问标记数组
int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1} };//方向数组
int cnt=0;//搜索过的格子数
void dfs (int x, int y) {
	递归什么时候终止?9个格子都搜索过了
	if(cnt == 9)	return;
	
	for(int i = 0;i<4;i++){//4个方向
		int nx = x + dir[i] [0];
		int ny = y + dir[i] [1] ;
		if((nx>=1&&nx <= 3&&ny>=1&&ny <= 3)&& !visited[nx] [ny]) {
			visited [nx] [ny] = true;
			cnt++;
			dfs (nx,ny);
		}
}
int main() {
	visited [1] [2] = true;
	cnt++;
	dfs(1,2);
	return 0;

}

DFS迷宫遍历(完整代码)

【编程要求】有一个3行3列共9个格子的迷宫,从(1,2)格子开始,按照下右上左的顺序进行深度优先搜索,走遍迷宫的9个格子。

cpp 复制代码
#include<iostream>
using namespace std;
//访问标记数组,false:没访问过,true:已经访问过
bool visited [4] [4] ;
//方向数组
//4行(4个方向),每两列,0列表示x方向的变化量,1列表示y方向的变化量。
int dir [4] [2]={{1,0},{0,1},{-1,0},{0,-1} }; //下右上左(如果想要看到搜索路径,在两个地方添加输出语句。)
//搜索过的房间数
int cnt=0;

//从(x,y)房间开始深度优先搜索
void dfs(int x, int y) {
	// 终止递归
	if (cnt == 9)	return;
	//递归搜索(x,y)当前房间的下右上左房间
	for(int i = 0;i<4;i++) {
		int nx = x + dir[i] [0] ;
		int ny = y + dir[i] [1] ;
		if((nx>=1 && nx <= 3 && ny>=1 && ny <= 3) && !visited [nx] [ny]) {
			visited [nx] [ny] = true;
			cnt++;
			dfs (nx,ny) ;//cout << "(" << nx << "," << ny << ") ";
		}
	}
}
int main () {
	visited [1] [2] = true;
	cnt++;
	dfs(1,2);//cout << "(1,2) ";
	return 0;
}

迷宫DFS的模版代码

适用于n行n列的迷宫(每个格子有4个方向可走):

走出迷宫

已知-N×N的迷宫,允许往上、下、左、右四个方向行走。入口在左上角(1,1)处,出口在右下角(N,N)处。每个格子有2种状态,0和1,前者表示可以通行后者表示不能通行。现在请你按照左、上、右、下顺序进行搜索,请问这个迷宫能不能走通。如果起点或者终点有一个不能通行(为1),则看成无法走通。

【输入描述】

输入数据有若干行,第一行有一个自然数N(N≤20),表示迷宫的大小,其后有N行数

据,每行有N个0或1(数字之间空格隔开),用以描述迷宫地图。

【输出描述】

能走通则输出"YES",否则输出"NO"。

【输入样例】

4

0 0 0 1

0 1 0 0

0 0 1 0

0 1 1 0

【输出样例】

YES

1、需要一个数组保存地图信息,0:可通行,1:不可通行。

cpp 复制代码
int n;

int mp[21][21];// 地图数组
bool visited[21][21];//标记数组

题目中格子的行列号从1开始,所以数组也从下标1开始用,定义时多留一个位置。(n≤20)

2、填写方向数组:本题要求 左上右下

cpp 复制代码
int dir [4] [2] = {{0,-1}, {-1,0 }, {0,1 }, {1,0} };

3、确定递归终止条件

走到出口位置(n,n)处,说明找到了出路,递归终止。

cpp 复制代码
bool flag=false;// 标记是否能走通
void dfs(int x, int y) {
	if ( x == n && y == n) {
		tlag = true;//能走通
		return;
	}
}

正确填出下面的表格,再填写方向数组

5、主函数调用dfs函数

  1. 输入n,和n行地图信息
  2. 先排除入口、出口不通的情况
  3. 从入口(1,1)开始dfs
  4. 输出结果
cpp 复制代码
int main() {
	cin >> n;
	for(int i=1;i <= n; i++) {
		for(int j=1;j <= n; j++)
			cin >> mp[i][j];
	}
	if (mp [1] [1] || mp[n] [n]) {
		cout << "NO";
		return 0;
	
	}
	
	visited [1] [1] = true;
	dfs (1,1);
	
	if(flag) cout << "YES";
	else		cout << "NO";
	
	return 0;
	
}

走出谜宫(完整代码示例)

cpp 复制代码
#include<iostream>
using namespace std;
int n;
int mp[21][21];//地图,0:可通过,1:不通
bool flag=false; //通路是否存在
//访问标记数组,false:没访问过,true:已经访问过
bool visited [21] [21] ;
//方向数组
//4行(4个方向),每两列,0列表示x方向的变化量,1列表示y方向的变化量。
int dir[4][2]={{0,-1},{-1,0},{0,1},{1,0}};//左上右下

//从(x,y)房间开始深度优先搜索
void dfs(int x, int y) {
	// 终止递归
	if (x == n && y == n) {
		flag= true;//走通
		return;
	}
	//递归搜索四个方向
	for(int i = 0;i<4;i++) {
		int nx = x + dir [i] [0] ;
		int ny = y + dir[i] [1];
		if((nx>=1 && nx <= n && ny>=1 && ny <= n) && !visited[nx] [ny] && mp[nx] [ny] == 0) {
			visited [nx] [ny] = true;
			dfs (nx,ny);
		}
	}
}
int main(){
	cin >> n;
	for(int i=1;i <= n;i++) {
		for(int j=1;j <= n;j++) {
			cin >> mp[i][j];
		}
	}

	if (mp [1] [1] || mp[n] [n]) {
		cout << "NO";
		return 0;
	}
	visited [1] [1] = true;
	dfs(1,1) ;
	
	if(flag) cout << "YES";
	else cout << "NO";
	return 0;
}

思考

如果其他编程要求不变,搜索顺序改变一下,例如,从"左上右下"变成"上下左右"。

代码如何改?

其实只要改方向矩阵就可以了,其他代码不变。

cpp 复制代码
上下左右的方向数组:

int dir [4] [2] = {{-1,0},{1,0 }, {o,-1}, { 0,1} };

迷宫的路径数量

设有一个N*N方格的迷宫,入口和出口分别在左上角和右上角。迷宫格子中分别放有0和1,0表示可通,1表示不能。迷宫走的规则如下:即从某点开始,有八个方向可走,前进方格中数字为0时表示可通过,为1时表示不可通过,要另找路径。找出从入口(左上角)到出口(右上角)的路径(不能重复)。输出路径总数。如果无法到达,输出0。

【输入描述】

第1行有一个整数N,表示迷宫大小(2≤N≤10)。

接下来N行,每行有N个数。

【输出描述】

输出一个整数,代表迷宫从起点到终点的路径条数。

【输入样例】

3

0 0 0

0 1 1

1 0 0

【输出样例】

2

问题分析
迷宫的路径数量(完整代码示例)
cpp 复制代码
#include<iostream>
using namespace std;
int n;
int mp[11][11];//地图,0:可通过,1:不通
int cnt;//通路数量
//访问标记数组,false:没访问过,true:已经访问过
bool visited [11] [11] ;
//方向数组
//8行(8个方向),每两列,0列表示x方向的变化量,1列表示y方向的变化量。
int dir[8] [2]={{-1,1}, {0,1}, {1,1}, {1,0} ,{1,-1}, {0,-1}, {-1,-1}, {-1,0}};

//从(x,y)房间开始深度优先搜索
void dfs (int x, int y) {
	// 终止递归
	if (x == 1 && y == n) {
		cnt++;//到出口。找到一条通路。
		return;
	}
	//递归搜索8个方向
	for(int i= 0;i<8;i++) {
		int nx = x + dir[i] [0] ;
		int ny = y + dir[i] [1] ;
		if((nx>=1 && nx <= n && ny>=1 && ny <= n) && !visited[nx] [ny] && mp[nx] [ny] == 0) {
			visited [nx] [ny] = true;
			dfs (nx,ny) ;
			//统计路径数量时,返回上一个格子前,当前格子visited[][]标记为false(未访问)。
			visited [nx] [ny] = false;
		}
	}
}
int main(){
	cin >> n;
	for(int i=1;i <= n;i++) {
		for(int j=1;j <= n;j++) {
			cin >> mp[i] [j];
		}
	}
	
	if (mp[1] [1] || mp[1] [n]) {//A/7(1,1) tHF7(1,n)
		cout << 0;
		return 0;
	}
	
	visited [1] [1] = true;
	dfs(1,1);
	cout << cnt;
	
	return 0;
}

本次分享的知识点

  1. 深度优先搜索(DFS)的概念
  2. 递归实现深度优先搜索
  3. DFS判断迷宫是否有通路
  4. DFS统计迷宫通路的数量
    1、深度优先搜索DFS的回溯机制是指?A

A、从一个节点返回到它的上一层节点;

B、从一个节点返回到它的下层节点;

C、从一个节点返回到它的相邻节点;

D、从一个节点返回到它的起点;

2、根据深度优先搜索思想,下图按照先左后右的顺序从1号开始搜索,搜索的路径是?(搜索过的点不再统计)C

A, 1-2-5-4-3-6-7-8

B、1-2-4-5-3-7-8-6

C、1-2-4-5-3-6-7-8

D、1-2-4-5-3-7-6-8

迷宫寻宝

已知山洞里面是由许多房间组成的迷宫,每个房间可以通往上下左右四个房间,迷宫大小是一个NN的正方形,其中有一些怪兽堵路。现在从起始(1,1)的位置进入洞穴寻找宝藏(已有一个宝箱),如果可以找到宝藏输出YES,否则输出NO。
【输入格式】第一行是一个正整数N(2<N≤10),后面包含N
N行,由0、1、2组成的矩阵,其中0表示可以走,1表示怪兽,2表示宝藏的位置。(注意:第一个房间没有怪兽)

【输出格式】找到宝藏输出YES,否则输出NO。

【输入样例1】

3

0 0 0

0 1 0

0 2 1

【输出样例1】YES

【输入样例2】

3

0 1 0

0 1 1

0 1 2

【输出样例2】NO

【代码示例】

cpp 复制代码
#include<iostream>
using namespace std;
int n;
int mp[11][11];//地图,0:可通过,1:不通,2:宝藏
bool flag=false;//通路是否存在
bool visited [11] [11] ;
int dir [4] [2]={{0,-1},{-1,0},{0,1},{1,0} }; //EET

//从(x,y)房间开始深度优先搜索
void dfs (int x, int y) {

	if(mp[x][y] == 2){//找到宝藏,终止递归
		flag = true;
		return;
	}
	//递归搜索四个方向
	for(int i = 0;i<4;i++) {
		int nx = x + dir[i] [0] ;
		int ny = y + dir[i] [1] ;
		//在地图范围内 && 没有访问过 &&不是怪兽
		if((nx>=1 && nx <= n && ny>=1 && ny <= n) && !visited[nx] [ny] &&| mp [nx] [ny] != 1)
			visited [nx] [ny] = true;
			dfs (nx,ny);
		}
	}
}
int main(){
	cin >> n;
	for(int i=1;i <= n;i++) {
		for(int j=1;j <= n;j++) {
			cin >> mp[i] [j];
		}
	}
	visited[1] [1] = true;
	dfs(1,1);
	
	if(flag) cout << "YES";
	else cout << "NO";
	return 0;
}
相关推荐
旖-旎1 小时前
《LeetCode 978 最长湍流子数组 || LeetCode 139 单词拆分》
c++·算法·leetcode·动态规划
时间的拾荒人1 小时前
C语言编译与链接:从源码到可执行程序的完整解析
c语言·开发语言
人道领域1 小时前
【0-1的agent进阶篇】Prompt 与上下文工程
java·开发语言·prompt·mcp
aaPIXa6221 小时前
C++大型项目模块化拆分实战记录
开发语言·c++
z落落1 小时前
C# WinForm 线程池与事件等待+进度条暂停恢复实战案例
开发语言·c#
石山代码1 小时前
C++23 新特性在 CLion 中的实战体验
开发语言·c++·c++23
浮江雾1 小时前
Flutter第四节------核心概念学习笔记:从基础语法到异步编程
linux·服务器·开发语言·笔记·flutter·入门·基础
在线OJ的阿川2 小时前
仿muduo库实现高并发服务器
linux·服务器·c++
北冥you鱼2 小时前
Go Modules 使用指南:从入门到精通
开发语言·后端·golang