ACM算法:深度优先搜索(附剪枝和优先队列的说明)

深度优先搜索的用途:针对迷宫型的搜救、最短路问题以及在数列中搜索符合某些要求的数集问题等等都可以用到深度优

先搜索,而且算法时间复杂度比寻常的枚举算法高效很多,代码也普遍更加精简,易于理解。本文我将会例举迷宫型和数列

型两种问题进行解释,这样大部分的搜索都与此类似。


深度优先搜索的思路:搜索其实就是一种遍历,只不过这种遍历更形象成一种树形结构,从最开始的根节点出发,一直到树

的尾端,在从“根”到“尾”的过程中,你就可以进行一些判断及操作。如果从“根”到其中一个“尾”都没有满足条件,那么就进行

回退,走另外一条路线。直到遇到符合条件的节点位置或者退出整个函数。

在这个过程中,你需要确定方向问题,比如在迷宫型问题中,你需要用一个二维的4*2数组标记上下左右四个方向(如果有

八个方向就定义8*2),以及对每个已经遍历的点进行标记。而且迷宫中有不同的起点需要比较,你对每个起点进行操作之

后还要注意还原每个点的状态,以便下一次的操作。当然这只是多见的几种情况,至于其他的情况,还需要你理解深度优先

搜索之后,自己去思考和实现。

讲到这你可能已经意识到了深度优先搜索需要用到递归。没错,递归确实是实现搜索的常用途径,但是在搜索中的递归很好理

解,所以不必要担心。除了递归实现之外,还有另外一种比较常用的实现,那就是优先队列,本文我会用例题分别解释这里种

实现具体应用。


例题解析:第一道例题被奉为经典,当然经典都是比较考验人的,但是并不代表这道题很难,相反,它的思路非常的明确。


Rescue

Problem Description

Angel was caught by the MOLIGPY! He was put in prison by Moligpy. The prison is described as a N * M (N, M <= 200) matrix. There are WALLs, ROADs, and GUARDs in the prison.

Angel's friends want to save Angel. Their task is: approach Angel. We assume that "approach Angel" is to get to the position where Angel stays. When there's a guard in the grid, we must kill him (or her?) to move into the grid. We assume that we moving up, down, right, left takes us 1 unit time, and killing a guard takes 1 unit time, too. And we are strong enough to kill all the guards.

You have to calculate the minimal time to approach Angel. (We can move only UP, DOWN, LEFT and RIGHT, to the neighbor grid within bound, of course.)

Input

First line contains two integers stand for N and M.

Then N lines follows, every line has M characters. "." stands for road, "a" stands for Angel, and "r" stands for each of Angel's friend. 

Process to the end of the file.
Output
For each test case, your program should output a single integer, standing for the minimal time needed. If such a number does no exist, you should output a line containing "Poor ANGEL has to stay in the prison all his life." 

Sample Input

7 8

#.#####.

#.a#..r.

#..#x...

..#..#.#

#...##..

.#......

........

Sample Output

13


这道题的题意应该不难理解,我这里简单的阐述一下。这道题其实就是让我们计算从“r”到“a”的最短距离,但

由于“r”可能有多个,所以我们逆向思维,计算从“a”到“r”的最短距离。


首先是递归实现

#include 
#include 
#include 
#include 
#include 
using namespace std;

char ves[410][410];
int move_dot[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };	//上下左右四个方向
int N, M;	//行列
int a_x, a_y;	//记录被拯救人a的位置
int MIN;	//MIN用于记录最小的数初始化
int sum = 0;	//记录每一种情况的步数
int flags = 0;	//用于标记是否能营救,0代表不行

//a和b分别表示‘a’的横纵坐标,坐标从0开始
void DFS(int a, int b)
{
	for (int i = 0; i < 4; i++) {
		int x = a + move_dot[i][0];
		int y = b + move_dot[i][1];

		if (x >= 0 && x < N && y >= 0 && y < M && ves[x][y] != '#') {

			if (ves[x][y] == '.') {
				sum++;
				ves[x][y] = '#';
				DFS(x, y);
				ves[x][y] = '.';	//还原操作
				sum--;
				continue;
			}

			else if (ves[x][y] == 'x') {
				sum += 2;
				ves[x][y] = '#';
				DFS(x, y);
				ves[x][y] = 'x';	//还原操作
				sum -= 2;
				continue;
			}
			else if (ves[x][y] == 'r') {
				sum++;
				MIN = min(sum, MIN);
				flags = 1;		//遇见r,说明至少存在一种可能
				sum--;
				return;
			}
		}
	}
}

int main(void)
{
	ios::sync_with_stdio(false);

	while (cin >> N >> M) {
		MIN = INT_MAX;
		
		for (int i = 0; i < N; i++)
			memset(ves[i], '\0', sizeof(ves[i]));

		for(int i = 0; i < N; i++)
			for (int j = 0; j < M; j++) {
				cin >> ves[i][j];
				if (ves[i][j] == 'a') {
					a_x = i;
					a_y = j;
				}
			}

		DFS(a_x, a_y);

		if (flags == 0) cout << "Poor ANGEL has to stay in the prison all his life." << endl;
		if (flags == 1) cout << MIN << endl;
	}

	return 0;
}

优先队列实现

//如果你还不了解什么是优先队列,建议先去搜索
//学习一下,优先队列不难理解,只是比普通队列
//多了一个按照某种规则的排序而已
//这段代码只满足只有一个R=r的情况,多个r只要稍加改动即可
#include 
#include 
#define max 200+10
using namespace std;
int n, m;
char map[max][max];
int sx, sy, ex, ey;
int dir[4][2] = { { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } };//上下左右四个方向


struct node
{
	int x, y, step;
	friend bool operator <(node a, node b)
	{
		return a.step>b.step;//优先队列,按照步数从小到大排列
	}
}a, temp;
int bfs()
{
	a.x = sx;
	a.y = sy;
	a.step = 0;
	priority_queueque;
	que.push(a);
	while (!que.empty())
	{
		a = que.top();//取出优先队列最前面的那个节点
		que.pop();
		if (a.x == ex&&a.y == ey)//找到一个r立即终止
		{
			return a.step;
		}
		for (int i = 0; i<4; i++)//四个方向
		{
			temp.x = a.x + dir[i][0];
			temp.y = a.y + dir[i][1];
			if (temp.x= 0 && temp.y= 0 && map[temp.x][temp.y] != '#')//满足条件的step增加
			{
				if (map[temp.x][temp.y] == '.' || map[temp.x][temp.y] == 'r')
					temp.step = a.step + 1;
				else
					temp.step = a.step + 2;
				map[temp.x][temp.y] = '#';
				que.push(temp);//压入点
			}
		}
	}
	return 0;
}
int main(void)
{
	int ans;
	while (cin >> n >> m)
	{
		for (int i = 0; i < n; i++)
			cin >> map[i];
		for (int i = 0; i

递归实现我就不多加解释了,结构还是很好理解的。我这里多讲一下优先队列。排序规则是按照步数最少的先出,

每次四个方向上的点入队,出队则表示这个点已经遍历过了。这样你每次取出的都是步数最少的那个点,知道你

遇见r的时候,这条路线也是最少的。


上面那道题是迷宫型的问题,下面这道题属于数列型的问题,只要你把这两种问题都理解透彻了,相信你已经对

搜索有一定的思考了。

Sticks

Problem Description

George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero. 

Input

The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.

Output

The output file contains the smallest possible length of original sticks, one per line. 

Sample Input

9

5 2 1 5 2 1 5 2 1

4

1 2 3 4

0

Sample Output

6

5

#include 
#include 
#include 
using namespace std;

int ves[65];
bool mark[65];
int N, sum, L, num;

bool compare(int a, int b)	//定义排序规则,由大到小
{
	return a > b;
}

bool DFS(int s, int length, int pos)
{
	bool flags = (length == 0 ? true : false);//用于剪枝操作

	for (int i = pos + 1; i < N; i++) {
		if (s == num) return true;		//结束条件
		if (mark[i]) continue;

		if (length + ves[i] == L) {
			mark[i] = true;
			if (DFS(++s, 0, -1)) return true;
			mark[i] = false;		//如果不是则把这个点还原
			return false;
		}
		if (length + ves[i] < L) {
			mark[i] = true;
			if (DFS(s, length + ves[i], i)) return true;
			mark[i] = false;
			if (flags) return false;
			while (ves[i] == ves[i + 1]) i++;//用于剪枝操作,判断是相同值的数则跳过
		}
	}

	return false;
}

int main(void)
{
	ios::sync_with_stdio(false);//一般用cin等C++输入输出操作容易超时,加上这个就不会了
	while (cin >> N) {
		if (N == 0) break;

		sum = 0;
		for (int i = 0; i < N; i++) {
			cin >> ves[i];
			sum += ves[i];
		}

		sort(ves, ves + N, compare);

		for (L = ves[0]; L <= sum; L++) {
			
			if (sum % L == 0) {	//首先L必须满足被sum整除,这个细想一下就知道了
				num = sum / L;//用于判断总共有多少条枝条
				memset(mark, false, sizeof(mark));	//每次都要初始化标记数组

				if (DFS(0, 0, -1)) {
					cout << L << endl;
					break;
				}
			}
		}
	}

	return 0;
}

这道题也是很经典的一道题,只是它不同上一题是,如果单纯使用搜索的话,会出现超时的情况,这个时候

我们就需要进行剪枝操作。很多初学者都以为剪枝是什么算法,其实不然,它只是对原有算法的基础之上,剔

除自己可以排除的情况。就像一棵树,减去不必要的枝条,以减少时间规模。


总结:搜索算法算是ACM入门必会的一种算法,它相对于其他算法而言还是挺易于理解的,只是需要不断的

练习才能熟练。




你可能感兴趣的:(ACM算法)