如题,本篇创作目的在于更精细化理解DFS的运作,篇幅不长,也只是作者的一家之言,只为提供一个对入门者的更精细的解释。

DFS,深度优先搜索算法,首先我们看中文,可以很清楚的理解到这个算法是指搜索操作中优先进行深度也就是纵向的数据筛查。

看搜索的基本思路:

360百科:

当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。

百度百科/维基百科(两个是一样的):

深度优先遍历图的方法是,从图中某顶点v出发:

(1)访问顶点v;

(2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;

(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。 当然,当人们刚刚掌握深度优先搜索的时候常常用它来走迷宫.

 那么按照我们粗浅的理解,提取关键信息和重复信息,我们可以得到以下概念:

1.重复性很高,很多操作都非常相似或者相同。

2.需要系统资源相对大,属于盲目搜索所有的可能信息,并且比较“笨拙”,也就是傻瓜式方法,所以有特殊限制条件。

3.每个结点都是有可预估方向的。

我们需要外引几个概念:树,决策树,递归,遍历。

首先树相信大家也并不陌生,当然不知道的最好赶紧补一补了嗷。决策树,解决问题常用,这里的使用用于理解。递归,递归这个东西很魔幻很玄乎,一定要好好学啊,太抽象了。遍历,一般是指树的遍历,这里我们暂时只用到中序遍历,不了解的建议补一下,这里暂时不多做说明。

这是一个标准二叉树 (只看到15)

中序遍历的结果:1-2-4-8-9-5-10-11-3-6-12-13-7-14-15

为什么我要说到中序遍历,看中序遍历结果,是不是顺序和深度优先字面意思符合呢?因为中序遍历是非常直观地展示DFS也就是深度优先算法的结果,并且符合决策树的顺序判断。例如我们测星座运势(或者性格测试,挺老套的),如图有8个结果,其中要经过两个问题,每个问题有两个选项,一共6种事件,所有结果产出走向为:

结果一: 1-2-4-8

结果二: 1-2-4-9

结果三: 1-2-5-10

......

结果七: 1-3-7-14

结果八: 1-3-7-15

可以发现如果划掉中序遍历的一部分结点标号,可以截出来所有结果。

深度优先搜索其核心就是先一路搜到底,然后返回上一个结点换条路继续一路搜到底

上述仅仅为DFS的一种,这里我们只讨论思路入门,不做过多实例。

为什么我们又提到了递归,递归的定义看似简单,就是函数本身的再调用,但是实际上水深着呢,比如说传参,可以不需要特地使用额外的容器保存数据就可以使用与递归调用相关的数据

为DFS举例: 单向迷宫

存在这样一个奇怪的迷宫,你需要闯关9个关卡,并且全部通关才能打开出口 ,默认你在1号关卡,问有多少种通关的路线?

其实这就是一个简单的九宫格图遍历的问题,我们先给出答案再做分析

代码:

#include<iostream>
using namespace std;
	
int k[3][3] = {1,2,3,4,5,6,7,8,9};
//关卡标号 
int square[3][3]={0};
//3*3的方格 
int x[4] = {0,1,0,-1};
int y[4] = {1,0,-1,0};
//记录下一步的坐标,x为水平,y为垂直,默认左上为正方向,搜索顺序为右下左上 
int total;
//int型默认值为0

void dfs(int xi,int yi,int step,string s)
{
	int tx,ty;
	//保存下一关卡坐标 
	if(step == 9)
	{
		total++;
		//9个关卡闯关完成为一条闯关路线,路线计数+1 
		cout<<"第"<<total<<"条搜闯关路线为: "<<s<<endl;
		//输出每条路线的具体闯关标号顺序 
		return ;
		//结束本层递归函数返回上一层 
	}
	for(int i=0;i<4;i++)//i<4因为四个方向,数组下标0开始 
	{
		tx = xi + x[i];
		ty = yi + y[i];
		//保存下一个关卡的坐标 
		if(tx<0||tx>=3||ty<0||ty>=3)
			continue;
			//如果下一关卡在地图外则跳过换一个方向 
		if(square[tx][ty]==1)
			continue;
			//如果下一关卡已经成功闯关则换一个方向 
		square[tx][ty]=1;
		//闯关本关,我更愿意理解为种草(要问就是1像草) 
		dfs(tx,ty,step+1,s+"->"+char('0'+k[tx][ty]));
		//递归,注意每次递归step+1指种草数 
		square[tx][ty]=0;
		//拔草 
	}
}
int main()
{
	square[0][0]=1;
	//从第一关开始 
	dfs(0,0,1,"1");
	//string s赋值1从第一关开始记路线 
	cout<<"一共"<<total<<"条闯关路线";
	return 0;            
}

结果:

基本每一行代码都有注释,可以说是很详细了,但是关于递归有一个非常重要非常女少口阿的点,我们分析一下题目闯关方式,用决策树分析一部分模型 

 

数字就是闯关标号,可以看到我只列举了两条完整的路线,其余的都是一层横向拓展的 

第一条路线: 1-2-3-6-9-8-7-4-5

第二条路线: 1-2-3-6-9-8-5-4-7

可以发现这两条路线差别就在于第八关的选择方向,这就需要提到这个递归的妙处了

void dfs(int xi,int yi,int step,string s)

xiyi应该不用多做解释,stepstring就有的说了

首先是stepstep的作用不仅仅在于标出当前的种草数(不知道的先去看看代码注释),它的关键在于当前递归层的层数,看一下递归的调用以及下一句:

dfs(tx,ty,step+1,s+"->"+char('0'+k[tx][ty]));
square[tx][ty]=0;

你可以看到你并没有保存step的具体数据,每一次的调用都是step+1,那么下一层递归的step-1就是当前层的step ,比如说此时step = 9了一条闯关路线结束return结束函数,那么回到上一层此时step = 8并且下一步拔草,具体点:

到第八关 8-> 有两条路线下一步是7或者是5

选择7:此时为一路运行到递归调用到的第七层 step = 7 接下来只有一条路->4->5,此时为递归第九层 step = 9 关卡5,因为直接调用递归所以递归下一句拔草并没有执行,等到return要结束最外一层递归的时候,最后一个关卡是没有种草的,也同时不需要拔草,接下来回到上一层 step = 8 关卡4,开始拔草,这次拔草拔的是这层种的草,拔完for循环继续发现其余方向都会continue,第八层递归结束回到上一层第七层 step = 7 关卡7,继续拔草拔本层的草,继续拔完for循环发现其余方向都是continue,第七层递归结束,回到上一层第六层 step = 6 关卡8,然后继续拔草把本层的草,继续拔完后for循环发现下一个方向可以走关卡5,于是继续调用递归开始寻找下一个闯关路线。

以上细节多多,第一遍可能看的有点混乱,多看几遍就好了。

string s 同理,每一层的step对应每一次不同的string s,每次递归回退都会消除外一层递归增加的内容。

DFS适合用于穷举计算,也就是穷尽每一种情况,傻瓜式的计算,其核心在于递归的使用,希望我上面的一些想法给你带来新的理解,如果你觉得我讲的不错可以点个赞支持一下另外如果绝对我哪里讲的不好或者有补充请在评论区说出

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐