首页 > 其他分享 >关于DFS

关于DFS

时间:2023-06-17 22:47:42浏览次数:36  
标签:return int dfs vis 枚举 DFS 关于 bool

概述

所谓深度优先搜索(以下称为dfs,depth first search),这个高尚的名字,它是什么呢?

我认为,他是一种借助计算机计算能力的枚举。

是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
剪枝,就是减小搜索树规模、尽早排除搜索树中不必要的分支的一种手段。形象地看,就好像剪掉了搜索树的枝条,故称之为“剪枝”。
from csdn

但是他又和普通枚举有什么区别呢?

dfs是基于递归实现的,从名字来看,这很能体现dfs的深度特点。

所以,dfs会从初态一直选择下面的其中一个状态扩展(体现为搜索树上的一个儿子),一直扩展到叶子节点(终态)。dfs的这种深度使他可以更快地接近目标

而普通枚举通常而言由于枚举方向的不确定性(或不易确定)而不方便进行枚举

是不是很不好理解?
来看一道题

一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由n * nn∗n的格点组成,每个格点只有2种状态,..和 # ,前者表示可以通行,后者表示不能通行。
同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点AA走到点BB,问在不走出迷宫的情况下能不能办到。
如果起点或者终点有一个不能通行(为#),则看成无法办到。

这是经典的走地图问题,为什么不好用普通枚举做呢?

如果用普通枚举,那么最好的方法就是设f[i][j]表示可以从i到j,最后枚举两两点,再从中间寻找一个中介点进行扩展(dp思想)

但是这样浪费也太多了,有些点完全八竿子打不着,但是我们还是要枚举。

如果用dfs,它每次只会尝试一条路径,每一步之间当然是很好确定的,降低了浪费和实现难度。

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int t,n;
bool vis[N][N];
int sx,sy,ex,ey;
const int dx[4]={0,0,1,-1},dy[4]={-1,1,0,0};
bool check(int x,int y){
	if(x<1||x>n||y<1||y>n||vis[x][y]) return false;
	else return true;
}
void readin(){
	cin>>n;
	memset(vis,0,sizeof vis);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			char ch;
			cin>>ch;
			if(ch=='#') vis[i][j]=1;
		}
	}
	cin>>sx>>sy>>ex>>ey;
	sx++,sy++,ex++,ey++;
}
bool dfs(int x,int y){
	if(x==ex&&y==ey){
		return true;
	}
	for(int i=0;i<4;i++){
		int nx=x+dx[i],ny=y+dy[i];
		if(check(nx,ny)){
			vis[nx][ny]=1;
			bool ret=dfs(nx,ny);
			if(ret) return true;
			vis[nx][ny]=0;
		}
	}
	return false;
}
int main(){
	cin>>t;
	while(t--){
		readin();
		bool ret=dfs(sx,sy);
		cout<<(ret?"YES\n":"NO\n");
	}
}

众所周知,dfs还有一个兄弟叫bfs,我简单罗列了他们的区别

X 实现 适用场景 思想区别
dfs 递归 一步步确定的问题,有前后依赖性 执着往下走,这使得有时dfs可能会有很大损耗(例如:网络流FF)
bfs 队列 广泛用于图上,或者可以转换为图的问题 一层层考虑,在一般没有分层这个概念时不适用(例如:枚举全排列)
具体的说明等到bfs总结在做

结构

一般的dfs结构

void dfs(参数){
	if(边界){
		操作
		return;
	}
	for(下一个状态){
		if(判断状态合法){
			标记
			dfs(下一个)
			取消标记
		}
	}
	return;
}

请注意边界条件,如果不正确很有可能引起递归爆栈MLE

经典应用

1.杂项

普通枚举可以解决的大部分问题dfs也可以解决。通常效率相差不大但dfs思想更为清晰(不过递归会带来常数上的损耗)。

2.图上求联通块

Link

由于近期的降雨,雨水汇集在农民约翰的田地不同的地方。我们用一个 \(N\times M(1\leq N\leq 100, 1\leq M\leq 100)\) 的网格图表示。每个网格中有水(W) 或是旱地(.)。一个网格与其周围的八个网格相连,而一组相连的网格视为一个水坑。约翰想弄清楚他的田地已经形成了多少水坑。给出约翰田地的示意图,确定当中有多少水坑。

只需枚举每一个点,以此扩展即可。

#include<bits/stdc++.h>
using namespace std;
char mp[101][101];
bool vit[101][101];
int dx[8]={0,0,-1,-1,-1,1,1,1};
int dy[8]={1,-1,-1,0,1,-1,0,1};
int n,m;
bool check(int x,int y)
{
	if(x<1||x>n||y<1||y>m||mp[x][y]=='.'||vit[x][y])
	{
		return 0;
	}
	else
	{
		return 1;
	}
}
void dfs(int x,int y)
{
	vit[x][y]=1;
	for(int i=0;i<8;i++)
	{
		int nx=x+dx[i],ny=y+dy[i];
		if(check(nx,ny)==1)
		{
			dfs(nx,ny);
		}
	}
}
int main()
{
	int ans=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
				cin>>mp[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(mp[i][j]=='W'&&!vit[i][j])
			{
				dfs(i,j);
				ans++;
			}
		}
	}
	cout<<ans;
}

优化

通常有5种方式:

  1. 最优化剪枝
  2. 可行性剪枝
  3. 排除等效冗余
  4. 记忆化
  5. 优化搜索顺序

其中1、2、4是较为常用的。

典例:

小木棍:

#include<bits/stdc++.h>
using namespace std;
int n,num[70],goal,cnt,sum,ans=0x3f3f3f3f;
bool vis[70];
inline bool dfs(int k,int len,int last)//last:最后失败 
{
	if(len==goal)
	{
		if(dfs(k+1,0,n))
		{
			return true;
		}
	}
	if(k>=cnt) return true;
	int cant=0;
	for(register int i=last;i>=1;i--)//last前面都已经失败 
	{
		if(!vis[i]&&len+num[i]<=goal&&cant!=num[i])
		{
			vis[i]=true;
			if(dfs(k,len+num[i],i-1))	return true;//考虑不搜索i-1之前的区间(i-1是因为排序) 
			//因为循环能执行到i,是因为之前的木棍都已经失败或被用过,虽然不能保证last之后的都没有访问过,但也不用再考虑
			 
			vis[i]=false;
			cant=num[i];//这个木棍失败
			if(len==0||len+num[i]==goal) return false;
			//len==0且失败时,说明当前方案不可能再拼接剩下的空木棍,不可能是答案
			//len+num[i]==goal且失败时,说明当前用一根能拼好目标失败,那用别的方法不仅会占用更多的短木棍,也不能得到成功 
		}
	}
	return false;
}
int main()
{
	cin>>n;
	int l=0;
	for(int i=1;i<=n;i++)
	{
		cin>>num[i];
		if(num[i]>50) 
		{
		n--;
		continue;	
		}
		l=max(l,num[i]);
		sum+=num[i];
	}
	sort(num+1,num+1+n);//先拼大的,占空间 
	for(register int i=l;i<=sum;i++)
	{
		if(sum%i==0)
		{
			goal=i;
			cnt=sum/i;
			memset(vis,false,sizeof vis);
			if(dfs(0,0,n))
			{
				cout<<i;
				return 0;
			}
		}
	}
	cout<<sum;
	return 0;
}

这道题需要同时使用剪枝123

关于“优化搜索顺序”:
这个比较少见。

[USACO07OCT]Obstacle Course S

#include<bits/stdc++.h>
using namespace std;
char mp[105][105];
int sx,sy,cnt,ans=0x3f3f3f,n;
int gox[4]={0,0,-1,1},goy[4]={-1,1,0,0};
int vis[105][105];
bool check(int x,int y)
{
	if(x<1||x>n||y<1||y>n||cnt>=vis[x][y]||mp[x][y]=='x') return false;
	else return true;
}
void dfs(int x,int y,int fx,bool fi)
{
	if(mp[x][y]=='B')
	{
		ans=min(ans,cnt);
		return;
	}
	if(cnt>=ans) return;
	for(int i=0;i<4;i++)
	{
		int nx=x+gox[i],ny=y+goy[i];
		if(check(nx,ny))
		{
			
			if(i!=fx&&!fi) cnt++;
			vis[nx][ny]=cnt;
			dfs(nx,ny,i,false);
			if(i!=fx&&!fi) cnt--;
		}
	}
	return;
}
int main()
{
//	freopen("1.in","r",stdin);
//	freopen("p1-4.out","w",stdout);
	cin>>n;
	memset(vis,0x3f,sizeof(vis));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			cin>>mp[i][j];
			if(mp[i][j]=='A')
			{
				sx=i;
				sy=j;
				vis[i][j]=0;
			}
		}
	}
	dfs(sx,sy,0,true);
	if(ans==0x3f3f3f) cout<<-1;
	else cout<<ans;
}

如果修改dxdy数组顺序就不能AC(其实这个是有点巧的事情)

关于记忆化搜索:

是dp的一种实现方式。此处可见dfs与枚举其实有很大关系。
有一个简单例子:
求斐波那契数列的第k项

int fib(int n){
	if(Ans[n]) return Ans[n];
	if(n==1||n==2) return n;
	Ans[n]=fib(n-1)+fib(n-2;
	return Ans[n];
}

将算过的先存起来,避免重复计算

EOF

感谢观看。\(\huge QwQ\)

标签:return,int,dfs,vis,枚举,DFS,关于,bool
From: https://www.cnblogs.com/haozexu/p/17488415.html

相关文章

  • 关于递归与分治
    关于递归众所众知,递归是思想而不是算法。从古至今,尽管人脑很高级,但好像人脑天生不适合模拟递归。它为什么难以被理解呢?我认为是它的这种自身调用自身的方式看似简单,但是实际上会建立一棵庞大的搜索树。人脑由于容易出错,而递归又是建立在上一层基础上的,所以可能越错越深。那它......
  • 关于BFS
    BFS目录Content概述问题思考与性质典型应用优化与扩展Part1概述I.什么是BFS?广度优先搜索(breadthfirstsearch),是以同层可达状态优先,一层层向外扩展的搜索算法。一般以队列实现II.算法基本结构图源:CSDN@sigdIII.动画过程演示通常,bfs会用在遍历图,下面的图生......
  • 关于KMP
    关于KMP平凡,而又不平凡的一天,12月31日,2022年的最后一天,让我们用几句代码迎接新年的到来。cout<<"Goodbye2022\n";printf("Hello2023!");扯正题。Kmp的简介KMP算法是字符串匹配算法,基础的用途是在文本串中快速查找与模式串相匹配的位置。一些感想我们在研究这个算法的......
  • 关于最小生成树
    关于最小生成树目录概述性质Prim算法实现例P1194买礼物Kruskal算法实现思想例P4047部落划分Part1概述一个连通图的生成树是一个极小的连通子图,它包含图中全部的n个顶点,但只有构成一棵树的n-1条边。我们定义无向连通图的最小生成树(MinimumSpannin......
  • 关于单调栈
    关于单调栈目录概述实现思想例一P5788[模版]单调栈例二P4147玉蟾宫Part1概述单调栈是一种其中元素具有单调性的线性结构。由于其栈的特性,这种结构可以用来处理左边\右边比它小\大的数。实际上,作者认为,遇到题目中元素:“限制”它的左、右且被其左、右“限制”的......
  • 路径之谜(DFS)-2016年蓝桥杯国赛
    路径之谜-2016年国赛1、题目描述2、解题思路3、代码实现1、题目描述  小明冒充X星球的骑士,进入了一个奇怪的城堡。  城堡里边什么都没有,只有方形石头铺成的地面。  假设城堡地面是n×n*个方格。如下图所示。  按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但......
  • 痞子衡嵌入式:主流QuadSPI NOR Flash厂商关于QE位与IO功能复用关联设计
    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家讲的是几家主流QuadSPINORFlash厂商关于QE位与IO功能复用关联设计。痞子衡之前写过一篇文章《串行NORFlash下载/启动常见影响因素之QEbit》,这篇文章介绍了几家主流厂商关于QEbit在Flash内部寄存器位置以......
  • AMBA2 关于APB
    参考https://zhuanlan.zhihu.com/p/419750074https://zhuanlan.zhihu.com/p/623829190注:波形图片来自于AMBA2APBProtocolSPEC.1.APB的用处APB不支持流水线设计,不支持突发传输。APB和AHB一样,有数据总线和地址总线,读写使用PWRITE和HWRITE控制,不能同时读写数据。......
  • 关于Cookie Session 和Token,以及应用场景
    关于Cookie和Session(面试经常问)共同之处:cookie和session都是用来跟踪浏览器用户身份的会话方式。关于会话在日常生活中,从拨通电话到挂断电话之间的一连串的你问我答的过程就是一个会话。Web应用中的会话过程类似于生活中的打电话过程,它指的是一个客户端(浏览器)与Web服务器之间连......
  • 关于js单线程的问题
    为什么说js是单线程?为了搞清楚这个问题,我们需要先了解这几个问题:什么是线程?什么是进程?他们之间的关系?什么是任务队列(EventQueue),任务分类(宏任务、微任务)?什么是事件循环?为什么说js是单线程?为什么js要是单线程?接下来我们一起来看一下:什么是线程?什么是进程?他......