讲解C++中的深度优先搜索(DFS)

9 篇文章 0 订阅

深度优先搜索是什么?

深度优先搜索是模拟的一种算法,属于搜索算法,相比于广度优先搜索的代码要短一点,但是它比广搜较难理解,毕竟人家的递归可不是吹的……深搜的想法是首先选取一个未访问的点作为源节点。从源节点选取一条路一直往下走,沿着当前顶点的边,访问这条路线,直到走不下去为止。这时返回上一顶点,继续试探访问此顶点的其余子顶点,直到当前顶点的子顶点都被访问过,那么,返回上一顶点,继续重复。从而实现遍历。

因此深搜有一句典型的句子来描述,就是:不撞南墙不回头!

深搜也属于递归的一种算法,主要可以解决的问题是,有多少种方案(计算方案数),判断能否到达终点,但是它无法算出共有几步,如果要计算几步的话,需要用广搜(广度优先搜索)

深度优先搜索模板

深度优先搜索的模板一般都是这样:

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 <stdio.h>
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;
} 

到这里,是不是讲的非常的通俗易懂,那还不赶紧点赞收藏。

好了,那今天就到这里啦,感谢您的支持,有什么不懂的问题也可以写在评论区里,

那我们下期再见吧(◕ᴗ◕✿),拜拜(。◕ˇ∀ˇ◕)

  • 20
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值