深度优先搜索DFS

DFS(深度优先搜索)

思想

用走迷宫举例,我们在走迷宫时遇到岔道就固定沿着一个方向走,如果发现没有路,则退回到上一次选择岔道的地方,改为另一条路,按照同样的法则走,这个方法执行的前提是要记住每次分叉时走过哪些岔路,不要重复走。
这是最简单的走法,简单来讲就是穷举暴力,将所有的路都试一遍。因此也非常费时,时间复杂度比较高,当然DFS也有很多优化技巧,比如剪枝。
DFS有递归和非递归两种实现方法,DFS就是回溯法,首先想到递归是很自然的。

例:Counting Sheep http://acm.hdu.edu.cn/showproblem.php?pid=2952

Description:

A while ago I had trouble sleeping. I used to lie awake, staring at the ceiling, for hours and hours. Then one day my grandmother suggested I tried counting sheep after I’d gone to bed. As always when my grandmother suggests things, I decided to try it out. The only problem was, there were no sheep around to be counted when I went to bed.

Creative as I am, that wasn’t going to stop me. I sat down and wrote a computer program that made a grid of characters, where # represents a sheep, while . is grass (or whatever you like, just not sheep). To make the counting a little more interesting, I also decided I wanted to count flocks of sheep instead of single sheep. Two sheep are in the same flock if they share a common side (up, down, right or left). Also, if sheep A is in the same flock as sheep B, and sheep B is in the same flock as sheep C, then sheeps A and C are in the same flock.

Now, I’ve got a new problem. Though counting these sheep actually helps me fall asleep, I find that it is extremely boring. To solve this, I’ve decided I need another computer program that does the counting for me. Then I’ll be able to just start both these programs before I go to bed, and I’ll sleep tight until the morning without any disturbances. I need you to write this program for me.

Input

The first line of input contains a single number T, the number of test cases to follow.

Each test case begins with a line containing two numbers, H and W, the height and width of the sheep grid. Then follows H lines, each containing W characters (either # or .), describing that part of the grid.

Output

For each test case, output a line containing a single number, the amount of sheep flock son that grid according to the rules stated in the problem description.

Notes and Constraints
0 < T <= 100
0 < H,W <= 100

Sample Input

2
4 4
#.#.
.#.#
#.##
.#.#
3 5
###.#
…#…
#.###

Sample Output

6
3

题解
简单的DFS求连通块,每个结点都有上下左右四个方向可以进行搜索,我们只需要按一个顺序(比如向上->向下->向左->向右)进行递归,并且用一个标记数组idx[]记录当前结点是否走过。如果走过该点或者走到了边界外,则返回。
注意,退出条件(图的边界或已走过该点),递归必须有退出条件,否则会进入死循环。
代码(递归实现)

#include<cstdio>
#include<cstring>
#define maxn 105
char pic[maxn][maxn];
int t, idx[maxn][maxn];
int h, w, cnt;

void DFS(int r, int c, int id)
{
	if (r < 0 || r >= h || c < 0 || c >= w)
		return;
	if (idx[r][c] > 0 || pic[r][c] != '#')
		return;
	idx[r][c] = id;
	DFS(r - 1, c, id);
	DFS(r + 1, c, id);
	DFS(r, c - 1, id);
	DFS(r, c + 1, id);
}

int main()
{
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &h, &w);
		for (int i = 0; i < h; i++)
			scanf("%s", pic[i]);
		memset(idx, 0, sizeof(idx));
		cnt = 0;
		for (int i = 0; i < h; i++)
			for (int j = 0; j < w; j++)
				if (idx[i][j] == 0 && pic[i][j] == '#')
					DFS(i, j, ++cnt);
		printf("%d\n", cnt);
	}
	return 0;
}

递归虽然可以将问题简化,但是递归是非常耗时的,一旦数据非常大,运算时间也是可想而知的,因此在某些时候我们把递归转化为非递归。学习了数据结构之后就可以了解到,栈具有后进先出的特点并且可以保存遍历过的结点,递归实际上是使用了系统调用栈,而如果不用系统调用栈,我们依然需要一个堆栈来保存状态,因此我们可以自己维护一个栈来保存所有未被访问的结点,将所有“符合条件”的“邻居”压入栈中备用,然后从周围结点继续深入,每个结点在访问之后被弹出。
代码(非递归实现)

#include<cstdio>
#include<cstring>
#include<stack>
#define maxn 255
using namespace std;
char pic[maxn][maxn];
int idx[maxn][maxn], t;
int h, w, cnt;
int dx[4] = { 1,-1,0,0 };
int dy[4] = { 0,0,1,-1 };
typedef struct
{
	int x;
	int y;
}Point;

void DFS_stack(Point start, int& id)
{
	stack<Point>s;
	s.push(start);
	idx[start.x][start.y] = 1;
	while (!s.empty())
	{
		Point now = s.top();
		s.pop();
		idx[now.x][now.y] = 1;
		for (int i = 0; i < 4; i++)
		{
			if (pic[now.x + dx[i]][now.y + dy[i]] == '#' && !idx[now.x + dx[i]][now.y + dy[i]])
			{
				Point t;
				t.x = now.x + dx[i];
				t.y = now.y + dy[i];
				s.push(t);
			}
		}
	}
}

int main()
{
	Point start;
	int t;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &h, &w);
		memset(pic, 0, sizeof(pic));
		for (int i = 0; i < h; i++)
			scanf("%s", pic[i]);
		memset(idx, 0, sizeof(idx));
		cnt = 0;
		for (int i = 0; i < h; i++)
		{
			for (int j = 0; j < w; j++)
			{
				if (idx[i][j] == 0 && pic[i][j] == '#')
				{
					start.x = i;
					start.y = j;
					DFS_stack(start, ++cnt);
				}
			}
		}
		printf("%d\n", cnt);
	}
	return 0;
}

优化方法

当然需要使用非递归写法的情况还是比较少的(我个人觉得),由于DFS就是暴力枚举,所以他的时间复杂度相应的也会较高O(n^x),而空间复杂度会相对较小。为了降低DFS的时间复杂度,也有很多的优化方法,最简单的就是剪枝。
常用的剪枝有:可行性剪枝、最优性剪枝、记忆化搜索、搜索顺序剪枝。

可行性剪枝

这是最简单的一种剪枝了,就是把不符合题意的部分剪掉,如果当前条件不合法就不再继续搜索,直接return。

最优性剪枝

如果当前状态得到的答案必定比之前的答案大,那么就没有必要再继续搜索,可以剪掉。

我们利用某个函数估计出此时条件下答案的‘下界’,将它与已经推出的答案相比,如果不比当前答案小,就可以剪掉。
一般实现:在搜索取和最大值时,如果后面的全部取最大仍然不比当前答案大就可以返回。
在搜和最小时同理,可以预处理后缀最大/最小和进行快速查询。

最优性剪枝的重点在于估计‘下界’,但注意,我们把‘下界’估计得越大,减去的状态就越多,如果估算得过大,超过了它的真的代价,就会把真正的解剪掉。

    if(check2(x)>=ans)
        return ...;
记忆化搜索

记录每个状态的搜索结果,当重复遍历到这个状态的时候直接使用之前记录的结果即可。我们在对图进行DFS的时候,用一个标记数组idx[]记录一个结点是否被访问过,其实就是一种记忆化。

搜索顺序剪枝

搜索顺序剪枝是一种类似于贪心思想的优化方法(个人认为),在一些迷宫、网络类题目中,搜索顺序的选择十分重要。
在上面的例题中,我们也会优先选择从羊(#)的地方开始搜索,而不是整个图里面随意乱搜,从已知信息多的地方开始搜,自然更好。
比如我们要从左上向右下搜索,很自然能想到遍历邻接点顺序右下左上更好一些。
再比如在一些题目中,先搜某个值大的,再搜某个值小的,速度自然会更快。


最后的碎碎念

其实搜索应该是DFS和BFS一起写比较好,但是第一次写博客有点无从下手最后只写了DFS,写得不好还请多多包涵orz
一直以来我都是在不断地接受知识,嘴比较笨也很少给同学讲东西,但是写博客的过程却感觉到自己对算法的思路逐渐变得有条理,突然感觉到了输出学习法的好处Σ(☉▽☉"a憋了一晚上最后能憋出点东西还是挺有成就感的。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值