深度优先搜索是模拟的一种算法,属于搜索算法,相比于广度优先搜索的代码要短一点,但是它比广搜较难理解,毕竟人家的递归可不是吹的……深搜的想法是首先选取一个未访问的点作为源节点。从源节点选取一条路一直往下走,沿着当前顶点的边,访问这条路线,直到走不下去为止。这时返回上一顶点,继续试探访问此顶点的其余子顶点,直到当前顶点的子顶点都被访问过,那么,返回上一顶点,继续重复。从而实现遍历。
因此深搜有一句典型的句子来描述,就是:不撞南墙不回头!
深搜也属于递归的一种算法,主要可以解决的问题是,有多少种方案(计算方案数),判断能否到达终点,但是它无法算出共有几步,如果要计算几步的话,需要用广搜(广度优先搜索)
深度优先搜索的模板一般都是这样:
void fun(int x,int y){
int tx,ty;
if(满足条件)
输出/方案数加一;
else
for(int i=0;i<4;i++){
tx=dx[i]+x,ty=dy[i]+y;
if(不越界,没走过,可以走){
标记走过;
fun(tx,ty);深搜
回溯(可有可无)
}
}
}
不过当然了,这只是一个模板,有的题目会在此基础上多加上一些内容
深搜的题目有这些:
NOI / 2.5基本算法之搜索1817:城堡问题
NOI / 2.5基本算法之搜索1792:迷宫(详细讲解)
NOI / 2.5基本算法之搜索 323:棋盘问题
还有就是,深搜有的是需要回溯的,如何判断需不需要回溯呢?其实,需要取能否到达目的地的题目是不需要回溯的,而求方案数的题目是需要回溯的。
来道题目试试水?
(首先,我们默认优先选择靠左或靠下的元素进行访问 )
选择A作为源节点------>(继续下一层深搜)访问到了B------->(继续下一层深搜)访问到了D------>(继续下一次深搜)搜索到了C------>(此时发现C的所有下一节点都已经被访问过了)返回C的上以节点D(因为是从D访问到C来的,所以不要走回到C的其他上节点)------>我们再遍历D的其他子节点E------->(发现E没有子节点)返回上一节点D------>(发现D没有未被访问的子节点)返回B------>(B没有被访问的子节点)返回A------>(A没有未被访问的子节点)DFS完毕
因此,此图的深搜遍历顺序:A—>B—>D—>C—>E
总结深搜的思路:沿着某条路径遍历,直到末端,然后回溯,再遍历另一条路径(走没有走过的岔路口)做同样的遍历,直到所有的节点都被访问,即回溯到源节点并且源节点已无未被访问的子节点。
举个例子:数的全排列问题,输入一个数n,输出1-n的全排列。
做如下分析:
我们把问题抽象:也就是说,有1-n个数字,要分别放到固定一列的n个盒子里,问分别有哪些不同的放法。
采用深搜的思想(不撞南墙不回头):
首先我们默认优先放置最小的小数字-----这步相当于选择先走靠左或靠下的路。
开始放置:1放在第一个,2放在第二个···n放在第n个,这样我们已经走到了头,所有的盒子我们都放过数字了。回溯一步并把第n个盒子里的数字n取出,我们当前处于第n-1个盒子处。当前盒子放的是n-1这个数字,我们把它取出来。这时,我们手里有n-1和n两个数字。采用最小数放置原则,但是数字n-1当前刚从这个盒子里取出来。为了避免重复,就不能再放进去了。所以,将目前未放置的数中除了n-1之外的最小数n放入第n-1个盒子里面,再往下走,走到了第n个盒子,手里剩下了一个数字n-1,自然放入第n个了。
此时我们已经拥有了两种排列:
1,2,3,···,n-1,n 和 1,2,3,···,n,n-1
继续回溯:当前在第n个盒子,回溯走到了第n-1个盒子(并取出了第n个盒子里的数字n-1),我们发现这两数字都在第n-1盒子放过,那就只能再回溯了(顺便拿起第n-1个盒子的数字n),走到了第n-2个盒子上,取出这个盒子的数n-2。当前,我们的状态时,在第n-2盒子上,手里有数字,n-2,n-1,n;那么遵循放小原则,并且不能重复,把n-1这个数字放入n-2这个盒子中,继续往下走,先把n-2放入第n-1盒子里,故第n个盒子放数字n了。
此时又出现了一种排列:
1,2,3,···,n-1,n-2,n;
再次从第n盒子回溯到n-1盒子:同理产生了 1,2,3,···,n-1,n,n-2 排列
因为不能往下走了,只能再次回溯到第n-2个盒子,放入n,········
不断重复如上操作,终会回溯到第一个盒子,第一个盒子把最大的数字n都放完之后,再次回溯到第一个盒子,第一个盒子已经无法放别的数字了。那便是全排列结束。
附上此题完整代码:
#include
int a[101];//盒子,目前只能放最多100个盒子,你可以自己加,但是要同时修改book的个数
int book[101]={0};//标记数组,标记是否已经放入
void dfs(int step,int n)//当前在第几个盒子上,总共有多少数
{
int i;
if(step==n+1){//如果站在第n+1个盒子面前了,那说明,已经放好所有的了
for(i=1;i<=n;i++){//输出这个组合
printf("%d ",a[i]);
}
printf("\n");
return;//返回上一层,也就是调用它的地方 (回溯)
}
for(i=1;i<=n;i++){//遵循最小原则 放入数字
//判断数字是否已经放入到之前的盒子
if(book[i]==0){//数字并未放入
a[step]=i;//放入
book[i]=1;//标记已经放入
//继续需要往下一个盒子走
dfs(step+1,n);
//走完这个盒子之后的所有操作,已经又回溯到这个盒子了,马上就要在此盒子放入下一个数了,所以我们需要将刚才的数字收回
book[i]=0;
}
}
return;
}
int main(){
int n;
scanf("%d",&n);
dfs(1,n);
return 0;
}
到这里,是不是讲的非常的通俗易懂,那还不赶紧点赞收藏。
好了,那今天就到这里啦,感谢您的支持,有什么不懂的问题也可以写在评论区里,
那我们下期再见吧(◕ᴗ◕✿),拜拜(。◕ˇ∀ˇ◕)