目录
首先在开始我们的学习之前,我们需要读者拥有递归的知识基础
并且在学习之前,我先给出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由于涉及到递归的思想,理解上还是比较困难的,大家学习完记得做相关的题目巩固知识点哟!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!