DFS(深度优先搜索算法)入门

本文详细介绍了深度优先搜索(DFS)的概念,并通过全排列问题、等式组合及迷宫问题三个实例,深入浅出地解释了DFS的运用。DFS是一种沿着树的深度遍历树的节点,尽可能深地搜索树的分支的算法。文章还提供了相应的C++代码示例,帮助读者理解DFS的实现过程。学习DFS的关键在于掌握“当下该怎么做”和“下一步如何做”。
摘要由CSDN通过智能技术生成

目录

导入

程序理解

例题


首先在开始我们的学习之前,我们需要读者拥有递归的知识基础

并且在学习之前,我先给出BFS的基本代码模型(基本大部分DFS题目都能按照此模型来写)

void dfs(int step){
	判断边界
	尝试每一种可能 for(i=1;i<=n;i++){
		继续下一步 dfs(step+1)
		}
	返回
}

下面开始我们的学习

导入

首先介绍一下DFS

所谓的DFS在我看来就是一条路走到黑,直到无路可走的情况下,才会选择回头,然后重新选择一条路(官方说法即“优先考虑深度”)

引导题:输入一个数n,输出n的全排列
可以先把这个问题形象化
如:

假如有编号为1,2,3的3张扑克牌和编号为1,2,3的3个盒子。将这3张扑克牌分别放入3个盒子一共有几种不同的放法呢?

OK,那么我们就按照DFS的定义“一条路走到底”

那么小明现在走到1号箱子面前,放入1号扑克;再走到2号箱子面前,放入2号扑克;走到3号箱子面前,放入3号扑克;好了我们已经成功“一条路走到底了”,这也是我们的第一种放法

完成第一种排列后,小明便要回头了,开始重新选择(DFS的思想),这里要注意,不是把所以的牌都取出来重新放,那样就不符合DFS的思想了

好的!按照上面的逻辑。小明将3号盒子的3号扑克牌取出,但手上仍然只有3号扑克牌。
所以小明只好回到2号盒子,收回2号扑克牌,此时手中有2,3号扑克牌。
按照之前的约定,往2号盒子放3号扑克牌,然后小明又继续往前走,此时手里只有2号扑克牌,把2号扑克牌放入3号盒子里,此时完成了第二种排序。
按照这种逻辑,我们不难求出所有排列“123”,“132”,“213”,“231”,“312”,“321”

程序理解

说了这么多,应该对DFS有了大概的了解吧。下面用程序来加深大家对DFS的认识。
可能大家会有疑惑,怎么才能往小盒子里放扑克牌呢?
其实很简单,只需要一个for循环就行了。

for(int i=1;i<=n;i++)
	a[step]=i;
	//将i号扑克牌放到第step个盒子中

 这里的数组a是用来表示盒子的,变量step表示当前处于第step个盒子面前。
这里还有一个问题:如果一张扑克牌已经放入别的盒子里了,该怎么样才能使这张扑克牌不放到当前的盒子里。
其实很简单,只需要重新创建一个数组book用来标记,看扑克牌是否被使用。

for(int i=1;i<=n;i++){
	if(book[i]==0){   
	//说明i号扑克牌还在手里,需要放入step号盒子
	a[step]=i;
	//将i号扑克牌放到第step个盒子中
	book[i]=1;
	//此时i号扑克牌已经被使用
		}
	}

那么接下来如何表示step+1呢?难道要一个函数一个函数这样写吗?其实不需要这么麻烦。
只需要把处理第step的代码封装成一个函数就可以了。

void  dfs(int step){ //此时在第step盒子面前,需要往里面放第i张扑克牌
	for(int i=1;i<=n;i++){
	if(book[i]==0){   
	//说明i号扑克牌还在手里,需要放入step号盒子
	a[step]=i;
	//将i号扑克牌放到第step个盒子中
	book[i]=1;
	//此时i号扑克牌已经被使用
		}
	}
}

接下就是处理第step+1的具体代码了

void  dfs(int step){ //此时在第step盒子面前,需要往里面放第i张扑克牌
 for(int i=1;i<=n;i++){
	if(book[i]==0){   
	//说明i号扑克牌还在手里,需要放入step号盒子
	a[step]=i;//将i号扑克牌放到第step个盒子中
	book[i]=1;//此时i号扑克牌已经被使用
		
	dfs(step+1);
	/*注意这里是自己调用自己,表示此时走到了第step+1个盒子面前*/		
	book[i]=0;
	/*book[i]=0表示dfs调用结束了,换句话说就是扑克牌已经全部放完了
	  需要按照顺序将扑克牌收回,重新放,也就是前面所说的
	 */
		}
	}
}

说到这里,是不是已经能够理解dfs。现在还要进行最后一步:程序结束的标志和完整的代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN=88;
int num[MAXN];//存储每个箱子里面的扑克牌号码
int book[MAXN]={0};//判断扑克牌是否使用过 
int n,total=0;
void dfs(int box)
{
	if(box==n+1)
	{
		for(int i=1;i<=n;i++)
		{
			printf("%d ",num[i]);
			
		}
		printf("\n");
		total++;
		return ;
	 } 	/* 
		注意这个 return 它的作用不是返回主函数,而是返回上一级的dfs函数
		
		例:如果此时是  dfs(5),遇到这个 return 就会回到上一级的 dfs函数 
		也就是dfs(4),但此时dfs(4)的大部分语句已经执行了,只需要接着执行 book[i]=0
		然后继续进入for循环进入下一次的 dfs函数,直到结束。 		
		*/ 

	for(int i=1;i<=n;i++)//遍历所有的扑克牌号码
	{
		if(book[i]==0)
		{
			num[box]=i;//将扑克牌放到箱子中 
			book[i]=1;//记录使用过的扑克牌 
			dfs(box+1);
			book[i]=0;//“将扑克牌拿出来 ” 		
		}

	 } 
	return ;//这里表示这一级别的dfs函数已经结束了,返回上一级 dfs函数 
}

int main()
{
	cin>>n;//输入我们手里有多少扑克牌	
	dfs(1);//从第一个箱子开始搜索 
	cout<<"总共有"<<total<<"种放法"<<endl;
	return 0;
}

相信大家已经能理解dfs了吧。其实要理解dfs的关键在于解决“当下该怎么做”和“下一步如何做

(如果仍然无法理解的话,最后自己一步一步调试来进行理解)

例题

例题 9个数凑一个等式,其中每个数由3个数字组成

思路

初看这个题目,其实最容易想到的其实是暴力枚举,如果用暴力枚举的话,则需要用到9重for循环,而且每个数都不能相同。在这里就不用这种方法了。
接下来就来看看dfs的用法吧(可以向右划)

#include<bits/stdc++.h>
using namespace std;

const int MAXN=88;
int num[MAXN];//存储每个箱子里面的扑克牌号码
int book[MAXN]={0};//判断扑克牌是否使用过 
int n,total=0;
void dfs(int box)
{
	if(box==10)//9个箱子都放入数字后 
	{
		if(num[1]*100+num[2]*10+num[3]+num[4]*100+num[5]*10+num[6]==num[7]*100+num[8]*10+num[9])//箱子里组成的数字能不能组成等式 
		{
			printf("%d+%d=%d\n",num[1]*100+num[2]*10+num[3],num[4]*100+num[5]*10+num[6],num[7]*100+num[8]*10+num[9]);
			total++;
		}
		return ;
	 } 
	
	for(int i=1;i<=9;i++)//遍历所有的扑克牌号码
	{
		if(book[i]==0)
		{
			num[box]=i;//将扑克牌放到箱子中 
			book[i]=1;//记录使用过的扑克牌 
			dfs(box+1);
			book[i]=0;//“将扑克牌拿出来 ” 		
		}

	 } 
	return ;
}

int main()
{
	dfs(1);//从第一个箱子开始搜索 
	cout<<"总共有"<<total/2<<"种放法"<<endl;
	return 0;
}

迷宫问题

题目大概意思:处于迷宫入口的小明(1,1),寻找位于(p,q)的小红,也就是最短路径问题 其中n表示行,m表示列
在这里插入图片描述

看到这个题目的时候用DFS,感觉就是四处碰壁,最后才走向成功。也就是指把所有的情况都发生一次,才知道最好的选择并输出。

首先要把这个迷宫看作一个二维数组,每走一步就相当于x或者y变化
在这里插入图片描述
接下来看看如何表示小明走一步吧!其实很简单,只需要用一个二维数组就行了哦。 

int next[4][2]={
 {0,1), //向右走一步
 {1,0},//向下走一步
 {0,-1},//向左走一步
 {-1,0}};//向上走一步

下面的一张图,就更方便大家理解了,正好与上面的源码对应。
在这里插入图片描述
通过这个方向数组,使用循环就很容易获得下一步的坐标。这里可以用tx,xy来表示。

for(k=0;k<=3;k++){   //下一步的坐标
	tx=x+next[k][0]; //下一步的坐标:当前的+下一步要走的
	ty=y+next[k][1];//下一步坐标:当前的+下一步要走的
}

 下面来对下一点(tx,ty) 进行一些判断。
包括是否越界,是否为障碍物,已经这个点是否被访问。所以也要一个book[tx][ty]来记录点是否被访问。如果这个点符合所有要求,就继续寻找下一个点,也就是 dfs( tx,ty,step+1)。
注意:a[][]等于0表示该点不是障碍
还有book[][]等于0表示该点还没走过
下面就是具体的代码

其实到这里也就差不多了,但还有最后一步。如何判断小明已经找到小红了。不多说了,看代码吧!

#include<iostream>
using namespace std;

int MIN = 10000;
int book[100][100] = { 0 };
int MAP[100][100];
int next_[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};//右 下 左 上
int tx, ty, p, q,n,m;

void dfs(int x, int y, int step)
{
	if (x == p && y == q)//找到小红 
	{
		if (step < MIN)
		{
			MIN = step;
		}
		return;
	}

	for (int z = 0; z <= 3; z++)
	{
		tx = x + next_[z][0];//下一步 
		ty = y + next_[z][1];//下一步 

		if (tx<1 || tx>n || ty<1 || ty>m)//是否越界 
		{
			continue;
		}

		if (book[tx][ty] == 0 && MAP[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			dfs(tx, ty, step + 1);
			book[tx][ty] = 0;
		}

	}
	return;


}


int main()
{
	int i, j, start_x, start_y;
	cin >> n >> m;//输入迷宫大小(n表示行,m表示列) 
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			cin >> MAP[i][j];
		}
	}
	cin >> start_x >> start_y;//输入“我的起点 ”
	cin >> p >> q;
	book[start_x][start_y] = 1;//初始化起点 
	dfs(start_x, start_y, 0);//从0步开始记录步数
	cout << MIN << endl;
	return 0;
}

总的来说,BFS由于涉及到递归的思想,理解上还是比较困难的,大家学习完记得做相关的题目巩固知识点哟!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZZWWWFFF_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值