深度优先搜索DFS+题目展示

深度优先搜索(DFS)

DFS(Depth-First Search)是搜索的手段之一。
它从某一个状态开始,不断地转移状态直到无法转移,然后退回到前一步地状态(很容易想到,要用递归实现),继续转移到其他状态,如此不断重复,直到找到最终的解。

比如求解数独,首先在某个格子内填入适当的数字,然后再继续在下一个格子内填入数字,如此重复下去。如果发现某个格子无解了,就放弃前一个格子上选择的数字,改用其他可行的数字。
下面的这个二叉树的遍历过程可以让你更加了解。
在这里插入图片描述
1->2->3->4–3->5–3--2->6–2--1->7->8->9–8->10–8--7->11
–抽象地表示返回前一状态

下面使用DFS解决一个问题:

                        部分和问题

给定整数a1,a2,a3,a4, …,an,判断是否可以从中选出若干数,使它们的和恰好为k.
limits:
1≤n≤20
-1e8≤n≤1e8
-1e8≤n≤1e8

样例1
Input:
n=4
a={1,2,4,7}
k=13
Output:
Yes (13 = 2 + 4 + 7)
样例2
Input:
n=4
a={1,2,4,7}
k=15
Output:
No

从a1开始按顺序决定每个数加或者不加,在全部的n个数都决定后在判断它们的和是不是k即可。因为状态数是2的n+1次幂,所以复杂度为O(2^n)。如何实现这个搜索,请参考下面的代码。注意a的下标与题目描述的下标偏移了1。
在这里插入图片描述

//input
int a[MAX_N];
int n,k;

//已经从前i项得到了和sum,然后对i项之后的进行分支
bool dfs(int i, int sum){
	//如果前n项都计算过了,则返回sum是否与k相等
	if(i == n) return sum == k;
	//不加上a[i]的情况
	if(dfs(i + 1, sum)) return true;
	//加上a[i]的情况
	if(dfs(i + 1, sum + a[i])) return true;
	//无论是否加上a[i]都不能凑成k就返回false
	return false;
}

void solve(){
	if(dfs(0, 0)) printf("Yes\n");
	else printf("No\n");
}

深度优先搜索从最开始的状态出发,遍历所有可以到达的状态。由此可以对所有的状态进行操作,或者列举出所有的状态。

下面再来一个POJ的题目:

                       Lake Counting (POJ No.2386)

有一个大小为N x M 的园子,雨后积了水。八连通的积水被认为是连接在一起的。请求出园子里总共有多少水洼?(八连通指的是下图中相对W的*的部分)
***
*W*
***
限制条件:
N,M<=100

样例
输入:
N=10, M=12 (‘W’表示积水,’.'表示没有积水)
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
输出:
3

从任意的W开始,不停地把邻接的部分用’.'代替。1次DFS后与初始的这个W连接的所有的W就都被替代为.。因此直到图中不再存在W为止,总共进行DFS的次数就是答案了。8个方向共对应了8种状态转移,每个格子作为DFS的参数至多被调用一次,所以复杂度为O(8 x N x m) = O(N x M)。

//输入
int N, M;
char field[MAX_N][MAX_M+1];   //园子

//现在位置(x,y)
void dfs(int x, int y){
	//将现在所在的位置替换为.
	field[x][y] = '.';
	
	//循环遍历移动的8个方向
	for(int dx = -1; dx <= 1; dx++){
		for(int dy = -1; dy <= 1; dy++){
			//向x方向移动dx,向y方向移动dy,移动的结果为(nx,ny)
			int nx = x + dx, ny = y + dy;
			//判断(nx,ny)是不是在园子里,以及是否有积水
			if(0 <= nx && nx <N && 0 <= ny && ny < M && field[nx][ny] == 'W') dfs(nx, ny);
		}
	}
	return ;
}

void solve(){
	int res = 0;
	for(int i = 0; i < N; i++){
		for(int j = 0; j < M; j++){
			//从有W的地方开始DFS
			if(field[i][j] == 'W'){
				dfs(i, j);
				res++;
			}
		}
	}
}

下面再来一个例子让你更加了解DFS

                         迷宫问题

给出一个n*m的迷图,一个入口,一个出口。
编写程序打印一条从入口到出口的路径。
-1表示不通。
0表示可以走。
只能往上、下、左、右这四个方向走。如果没有路则输出"no way."。
样例:
输入:
8 5
-1 -1 -1 -1 -1
0 0 0 0 1
-1 -1 -1 0 -1
-1 0 0 0 -1
-1 0 0 1 -1
-1 0 0 0 -1
-1 -1 -1 0 -1
-1 0 0 0 -1
输出:



代码:(完整)

#include <iostream>
using namespace std;
int n,m,desx,desy,soux,souy,totstep,a[51],b[51],map[51][51];
bool f;
int move(int x, int y, int step)
{
	map[x][y] = step;         //走一步,作标记,把步数记下来
	a[step]=x;				  //保存路径,为了后续的输出
	b[step]=y;
	if((x==desx)&&(y==desy))
	{
		f=1;
		totstep=step;
	}
	else
	{
		if((y!=m)&&(map[x][y+1]==0))        move(x,y+1,step+1);//向右
		if((!f)&&(x!=n)&&(map[x+1][y]==0))  move(x+1,y,step+1);
		//向下
		if((!f)&&(y!=1)&&(map[x][y-1]==0))  move(x,y-1,step+1);
		//向左
		if((!f)&&(x!=1)&&(map[x-2][y]==0))  move(x-1,y,step=1);
		//向上
	}
}

int main()
{
	int i,j;
	cin>>n>>m;                     //n行m列
	for(i=1;i<=n;i++)              //读入迷宫,0表示通,-1表示不通
		for(j=1;j<=m;j++)
			cin>>map[i][j];
	cout<<"Input the enter:";
	cin>>soux>>souy;               //入口
	cout<<"Input the exit:";
	cin>>desx>>desy;			   //出口
	f=0;                           //f=0表示无解。f=1表示找到一个解
	move(soux,souy,1);
	if(f)
	{
		for(i=1;i<=totstep;i++)     //输出走迷宫的路径
			cout<<a[i]<<","<<b[i]<<endl;
	}
	else    cout<<"no way."<<endl;
	return 0;
}

上面有个要注意的就是上下左右的优先选择。
以入口在左上,出口在右下,为例(默认找到时退出,在下面的过程中就省略不写了)
已经访问过的节点会被作标记,看代码map[x][y] = step;
这里给了那个节点一个不为0的值,既认为该节点不通。

先一直向左移,直到不通或者到边缘。然后再向下。直到不通或到边缘。然后再向左,然后再向上。

想象一下,如果还有上面的那个模式,但是出口和入口换了一个位置,那么我们向左和向下的这个过程,其实对我们求解这个问题是没有帮助的(虽然让我们排除了一定的范围,但是在测试的环境中,我们已经知道了出口和入口的位置了)。
策略的选择影响效率。

还有,这个一般都是先判断再移动。而不是像上面那个二叉树那样还要回溯(先移动,再判断,符合继续移动,不符合则要返回上一个节点)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值