关于DFS

本文介绍了深度优先搜索(DFS)的基本思想,将其比喻为“一条道走到黑”,强调了DFS在不考虑最短路径的情况下的枚举特性。文中列举了DFS的典型应用场景,包括AcWing上的排序数字问题和n-皇后问题,并提供了相应的代码实现。还提到了如何判断是否需要回溯以及如何设计搜索图来辅助理解DFS。最后,文章通过牛客网的两道题目展示了DFS解决连通块问题的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DFS也学了有一段时间了,虽然相关的题目还是有点写不出来,但是其基本思想还是可以总结一二的。

DFS,深度优先搜索,通俗的说,就是 “一条道走到黑” ,“不撞南墙不回头”。它可以把所有可能的类型枚举出来,是不具有最短路效应的。

DFS算法中,str[N]数组是用来存放各元素的状态的,即 用来判断是否被遍历过 。其目的是,不再重复的遍历。

DFS中还有个词叫做 回溯 ,它就是追溯到当前支路上最深的位置,无法再继续深追,然后原路返回,并把当前的str数组恢复到原来的状态的过程。

(并非每个DFS需要回溯,想判断是否需要回溯,就看是否有必要重复遍历,若没必要重复,就可以不需要str回溯了) 

首先,要确定枚举的顺序,即 是依据什么来分类的 。(这个很重要,它可以影响你整个代码的运行效果)

然后,就是画搜索图。(这个的话,只是为了像我一样的初学者更好理解DFS这个算法)

大致思路确定后就开始去实现了。

DFS大部分都是模板题。但是模板分很多种 ,我道行不够,暂时总结不了,等我再去练习一段时间吧。下边再加几个经典的例题,加深记忆。

1.0 Acwing.842 排序数字

活动 - AcWing

这题我以它三位数确定的顺序来枚举。

如,我先确定第一个数,它可能为1,可能为2,可能为3,分为了三个分支(下图)。

若第一个数为1,那第二个数可能为2,也可能为3,这就又分成了两个分支。

后面依此类推。

它的搜索图长这个样子

代码如下:

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 10;
int n;
int path[N],str[N];//path数组用来记录排列的顺序,str数组用来记录状态(是否经过了)

void dfs(int u)
{
    if(u==n)//返回条件
    {
        for(int i=0;i<n;i++)
            cout<<path[i]<<" ";
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(!str[i])
        {
            str[i]=1;//记录已经历的点
            path[u]=i;
            dfs(u+1);
            str[i]=0;//复原状态
        }
    }
}

int main()
{
    cin>>n;
    dfs(0);//从0开始
    return 0;
}

 2.0 Acwing.843 n-皇后问题

活动 - AcWing

代码如下: 

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 11;
int n,str[N][N];
char q[N][N];
int h[N],d[N],f[N];//一行、主对角线、副对角线 
void dfs(int u)
{
	if(u==n)
	{
		for(int i=0;i<n;i++)
			puts(q[i]);
		cout<<endl;
		return;
	}
	for(int i=0;i<n;i++)
	{
		if(!h[i]&&!d[u+i]&&!f[n-u+i])
		{
			q[u][i]='Q';
            h[i]=d[u+i]=f[n-u+i]=1;
            dfs(u+1);
            h[i]=d[u+i]=f[n-u+i]=0;
            q[u][i]='.';
		}
	}
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
			q[i][j]='.';
	}
	dfs(0);
}

3.0 牛客小白月赛 70 - C 小d和超级泡泡堂

登录—专业IT笔试面试备考平台_牛客网

Input 

4 4
..!.
.@.#
!##!
#!!!


Output

2

这题我以一个点的上下左右进行分类。

它的搜索图即为

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1111;
char q[N][N];//存放地图 
int n,m,ans;
int str[N][N];//记录状态 
int x1[]={0,0,-1,1};
int y1[]={1,-1,0,0};//x1,y1用来控制上下左右 
void dfs(int u,int r)
{
	int x,y;
	str[u][r]=1;//标记好已遍历的点 
	for(int i=0;i<4;i++)
	{
		x=u+x1[i];//在整个dfs函数中,u、r对应的是当前的点 
		y=r+y1[i];//x、y对应的则是下一个点的位置 
		if(x>=0&&x<n&&y<m&&y>=0&&!str[x][y]&&q[x][y]!='#')
		{//在地图里、没有被标记过、不是石头 
			dfs(x,y);
			if(q[x][y]=='!')//若为草,则ans直接加一 
				ans++;
		}
	}
}
int main()
{
	int a,b;
	cin>>n>>m;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			cin>>q[i][j];
			if(q[i][j]=='@')//找@所在的位置 
			{
				a=i;
				b=j;
			}
		}
	}
	dfs(a,b);//从@点开始 
	cout<<ans<<endl;
	return 0;
}

这是一个 连通块 类型的问题,与之前的全排问题有所区别。

4.0 牛客-璐神看岛屿

登录—专业IT笔试面试备考平台_牛客网

题目描述

璐神现在有张n*m大小的地图,地图上标明了陆地(用"#"表示)和海洋(用"."表示),现在璐神要计算这张地图上岛屿的数量。

已知岛屿是由陆地的连通块组成,即一块陆地的上、下、左、右,左上,右上,左下,右下有其他陆地,则构成连通块,以此类推。

此外,岛屿的详细定义如下:

1、岛屿的周围必须全是海洋。

2、如果连通块有任意区域在地图边界,则该连通块不是岛屿

Input

3 3
...
.#.
...

Output

*样例说明:只有中间的1块陆地是岛屿,所以岛屿数=1

Input

3 3
#..
.#.
...

Output

0

*样例说明:中间的连通块有区域在边界,所以不是岛屿,岛屿数=0。

这题的题目意思很重要。要重点看岛屿的定义,以及样例。

一个连通块有8个方向,这都属于它的区域。

地图边界不能存在连通块,否则则不属于岛屿。 

*continue:跳过此次循环,进行下一次循环

*break:跳出一个循环体或者完全结束一个循环 

代码如下:

#include<iostream>
#include<algorithm> 
using namespace std;
const int N = 222;
char map[N][N];
int n,m,ans,sum;
int str[N][N];//记录状态 
int x1[]={0,0,-1,1,-1,-1,1,1};
int y1[]={-1,1,0,0,-1,1,-1,1};//上、下、左、右、左上、左下、右上、右下 
void dfs(int u,int v)
{
	int x,y;
	if(u==0||u==n-1||v==0||v==m-1)	sum=1;//在边界时 
	str[u][v]=1;
	for(int i=0;i<8;i++)//八个方向 
	{
		x=u+x1[i];
		y=v+y1[i];
		if(x>=0&&x<n&&y>=0&&y<m&&!str[x][y]&&map[x][y]=='#')
			dfs(x,y);//在地图上、没有遍历过、是一个陆地 
	}
}
int main()
{
	cin>>n>>m;
	for(int i=0;i<m;i++)
		cin>>map[i];
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			sum=0;
			if(map[i][j]!='#'||str[i][j])	continue;//遇到海洋或者已经遍历过的点,直接跳过 
				dfs(i,j);
			if(!sum)//不在边界 
				ans++;
		}
	}
	cout<<ans<<endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值