深度优先搜索(DFS)

深度优先搜索:

就是从初始状态出发,下一步可能有多种状态;选其中一个状态深入,到达新的状态;直到无法继续深入,回退到前一步,转移到其他状态,然后再深入下去。最后,遍历完所有可以达到的状态,并得到最终的解。

1.相比广度优先搜索(BFS)的DFS的代码要短的多。拿一道很经典的搜索题来说

hdu 1312 Red and Black

描述

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入

包括多个数据集合。每个数据集合的第一行是两个整数W和H,分别表示x方向和y方向瓷砖的数量。W和H都不超过20。在接下来的H行中,每行包括W个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:白色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。

输出

对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

样例输入

6 9 
....#. 
.....# 
...... 
...... 
...... 
...... 
...... 
#@...# 
.#..#. 
0 0

样例输出

45

思路:

这道题BFS和DFS都可以用而且思路都很简单,只要有路就往下走,要是没路的话返回上一步换个方向继续走,直到不能走为止,此题的话用DFS的方法代码要短很多。

代码如下:

一、BFS

#include <iostream>
#include <queue>
using namespace std;
char room[23][23];
int dir[4][2] = {
	{-1,0},      //向左
	{0,-1},      //向上
	{1,0},       //向右
	{0,1}        //向下
};
int Wx, Hy, num;       //Wx行,Hy列 用num统计可走的位置有多少
#define CHECK(x,y) (x<Wx&&x>=0&&y>=0&&y<Hy)   //是否在room中
struct node { int x, y; };
void BFS(int dx, int dy) {
	num = 1;
	queue<node>q;    //队列中放坐标点
	node start, next;
	start.x = dx;
	start.y = dy;
	q.push(start);
	while (!q.empty()) {
		start = q.front();
		q.pop();
		//cout <<"out"<<start.x<<start,y<<endl;
		for (int i = 0; i < 4; i++) {    //按左、上、右、下4个方向顺时针逐一搜索
			next.x = start.x + dir[i][0];
			next.y = start.y + dir[i][1];
			if (CHECK(next.x, next.y) && room[next.x][next.y] == '.') {
				room[next.x][next.y] = '#';       //进队之后标记为已经处理过
				num++;
				q.push(next);
			}	
		}
	}
}
int main() {
	int x, y, dx, dy;
	while (cin >> Wx >> Hy) {            //Wx行、Hy列
		if (Wx == 0 && Hy == 0)          //结束
			break;
		for (y = 0; y < Hy; y++) {      //有Hy列
			for (x = 0; x < Wx; x++) {   //一次读入一行
				cin >> room[x][y];
				if (room[x][y] == '@') { //读入起点
					dx = x;
					dy = y;
				}
			}
		}
		num = 0;
		BFS(dx, dy);
		cout << num << endl;
	}
	return 0;
}

二、DFS

#include <iostream>
using namespace std;
int  Wx, Hy;
int num;
char room[23][23];
#define CHECK(x,y) (x>=0&&x<Wx&&y>=0&&y<Hy)
int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };
void dfs(int dx, int dy)
{
	room[dx][dy] = '#';
	num++;

	for (int i = 0; i < 4; i++)
	{
		int newx = dx + dir[i][0];
		int newy = dy + dir[i][1];
		if (CHECK(newx, newy) && room[newx][newy] == '.')
		{
			dfs(newx, newy);
		}
	}
}
int main()
{
	int dx, dy, x, y;
	while (cin >> Wx >> Hy) {
		if (Wx == 0 && Hy == 0)
			break;

		for (y = 0; y < Hy; y++)
		{
			for (x = 0; x < Wx; x++)
			{
				cin >> room[x][y];
				if (room[x][y] == '@')
				{
					dx = x;
					dy = y;
				}
			}
		}
		num = 0;
		dfs(dx, dy);
		cout << num << endl;
	}
	return 0;
}

两种方法的主函数都是相同的,相比之下可见用DFS写的话代码要短很多,不过个人感觉BFS更好理解

2.对于DFS来说有一个不方便的地方就是:因递归列举出所有的路径可能会因为数量太大而超时,由于很多结点是不符合条件的,故需要用到“回溯”,也就是“看到不对头就撤退”,中途停止并返回。以下题为例

poj 2488 A Knight's Journey

描述

背景
骑士厌倦了一遍又一遍地看到相同的黑白方块,并决定
环游世界。每当一个骑士移动时,它是一个方向上的两个方格和一个垂直于这个方向的方格。骑士的世界就是他赖以生存的棋盘。我们的骑士住在一个棋盘上,棋盘的面积比普通的 8 * 8 棋盘要小,但它仍然是矩形的。你能帮助这个冒险的骑士制定旅行计划吗?

问题
找到一条路径,使骑士访问每个方格一次。骑士可以在棋盘的任何方格开始和结束。

输入

输入以第一行的正整数 n 开头。以下几行包含 n 个测试用例。每个测试用例由一行包含两个正整数 p 和 q 组成,使得 1 <= p * q <= 26。这表示 ap * q 棋盘,其中 p 描述了多少个不同的平方数 1, 。. . , p 存在, q 描述存在多少个不同的方形字母。这些是拉丁字母表的前 q 个字母:A、. . .

输出

每个场景的输出都以包含“Scenario #i:”的行开始,其中 i 是从 1 开始的场景编号。然后打印一行,其中包含按字典顺序访问棋盘所有方格的第一条路径,然后是骑士移动由空行。路径应该通过连接访问过的方块的名称在一行中给出。每个方块名称由一个大写字母后跟一个数字组成。
如果不存在这样的路径,您应该在一行上输出不可能。

样例输入

3
1 1
2 3
4 3

样例输出

Scenario #1:
A1

Scenario #2:
impossible

Scenario #3:
A1B3C1A2B4C2A3B1C3A4B2C4

 思路:

任选一个起点,按照国际象棋马的跳法,不重复的跳完整个棋盘,如果有多种路线则选择字典序最小的路线(路线是点的横纵坐标的集合,注意棋盘的横坐标的用大写字母,纵坐标是数字)

首先要明白这个题的意思是能否只走一遍(不回头不重复)将整个地图走完,而普通的深度优先搜索是一直走,走不通之后沿路返回到某处继续深搜。所以这个题要用到的回溯思想,如果不重复走一遍就走完了,做一个标记,算法停止;否则在某种DFS下走到某一步时按马跳的规则无路可走而棋盘还有为走到的点,这样我们就需要撤消这一步,进而尝试其他的路线(当然其他的路线也可能导致撤销),而所谓撤销这一步就是在递归深搜返回时重置该点,以便在当前路线走一遍行不通换另一种路线时,该点的状态是未访问过的,而不是像普通的DFS当作已经访问了。

代码如下:

#include <stdio.h>	
#include <string.h>
#include <iostream>
using namespace std;
int book[10][10];
int arr[8][2] = { {-1,-2},{1,-2},{-2,-1},{2,-1},{-2,1},{2,1},{-1,2},{1,2} };//八个方向,
//根据途中马的位置,因为是字典序,所以它必须先走左上角,所以这八个方向左边优先
int a, b;
int res[100][2];//用来存xy是第几步走的
int found;//找到一个可以执行通道的标志
#define check(x,y) (x < 0 || x >= a || y < 0 || y >= b)		
void dfs(int m, int n, int depth)
{
	res[depth][0] = m;
	res[depth][1] = n;
	if (depth == a * b)
	{
		found = 1;
		for (int i = 1; i <= a * b; i++)
		{
			printf("%c%d", res[i][1] + 'A', res[i][0] + 1);
		}
		cout << endl;
		return;
	}
	int k;
	int tx, ty;
	for (k = 0; k < 8; k++)
	{
		tx = m + arr[k][0];
		ty = n + arr[k][1];
		if (check(tx,ty)||book[tx][ty] != 0)
			continue;
		if (found)
			break;
		book[tx][ty] = 1;
		dfs(tx, ty, depth + 1);
		book[tx][ty] = 0;
	}
}

int main()
{
	int n;
	cin >> n;
	int count = 0;
	while (n--)
	{
		memset(book, 0, sizeof(book));
		found = 0;
		count++;
		cin >> a >> b;
		cout << "Scenario #" << count << ":" << endl;
		book[0][0] = 1;
		dfs(0, 0, 1);
		if (!found)
		{
			cout << "impossible" << endl;
		}
		if (n != 0)
			cout << endl;
	}
}

大数据 liyang

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值