啊哈算法第四章 万能的搜索

从这一章开始就真的步入对我而言全新的算法世界了(因为排序、枚举在上学期还是接触过的,而栈、队列在这学期我看的C++Primer和老师教的Java课里也有所涉及)

一、深度优先搜索Depth First Search)

首先作者用上章遗留的全排列问题作为引例为我们介绍了DFS是啥东西。

题目1:输入一个数n 请输出1~n的全排列。

当然我们可以用上一章的枚举来做,并且用一个book[10]的数组还能减少一些麻烦,但其实还是很麻烦……而用dfs则简单得多。上代码。

#include
using namespace std;
int a[10], book[10], n;
void dfs(int step)   //现在站在第step个盒子前。   
{	
	if (step == n + 1)
	{
		for (int i = 1; i <= n; ++i)
			cout << a[i];
		cout << endl;
		return;
	}
	for (int i = 1; i <= n; ++i)
	{
		if (book[i]==0)    //牌在手上 
		{
			a[step] = i;   //放入盒子中
			book[i] = 1;     //牌不在手上了
			dfs(step + 1);     //走向下一个盒子
			book[i] = 0;     //把刚才尝试的牌都收回来,再进行下一次的排列尝试
		}
	}
	return;
}
int main()
{
	cin >> n;
	dfs(1);
	system("pause");
}

其实主要要注意的是其中的递归问题

分析

首先我们站在第一个盒子(作者将每一个数比作一个对应的盒子)面前,所以main函数中是dfs(1),在dfs函数中,我们将1到n每一个数都进行尝试,如果这个牌仍在手中,则放入盒子里,然后走向下一个盒子(dfs(step+1)),直到走到了n+1个盒子,当然这个盒子不存在,也就是说我们已经走到了尽头,便将排好的一个排列输出,return。作者说必须马上return,但其实并不需要,不会陷入死循环,因为在下方遍历1~n时并没有牌在手中,所以并不会继续dfs(step+1) 而是仍会执行最下面的return语句。(但是,最好还是写上那条return语句,因为可以不用再去遍历循环以提高效率,并且,if(step==n+1)这一语句块是递归的结束条件,一般来说在结束条件都要return以避免死循环的 所以养成好习惯不是坏事~)

当执行完一次排列后(也就是一次step到n+1后),return,则返回到上一次调用dfs的地方,也就是循环中的dfs(step+1)这一语句,其实这就是递归啦,那么下一句就是book[i]=0; 即收回刚才放入盒子中的牌,然后一直递归, 一直收牌,直到可以有不同排列时停止,此时便进行下一种排列。(这里最好自己脑子里想一遍全过程,下一段落我也给出了过程帮助理解)这也就是为什么作者说book[i]=0;这一语句十分重要的原因,如果不收回牌,则无法进行下一次排列。  最后,最后一条语句return也不要忘了。。

嗯,这里还是给一小部分过程帮助理解吧。比如要给出1到3的全排列。 最开始,我们是站在第一个盒子面前,从1~n遍历,因为有1,所以放进盒子中,手里也就没有1了,然后走向下一个盒子,从1~n遍历,没有1,但是有2,将2放入盒子中,手中没有2了……最后走到第四个盒子时,刚好放入了123,输出123,返回到上一次调用dfs函数的地方,即在第三个盒子处,我们将盒子三里的牌收回来(即3),因为在这时循环i==3 所以++i后循环结束,return 又到了上一个调用dfs函数的地方,则是在第二个盒子处,收回在第二个盒子里的牌(即2),此时循环i==2,++i,则i==3,因为我们有3,所以将3放在第二个盒子的位置,走向第三个盒子,从1~n遍历,因为有2,所以将2放入第三个盒子,然后走向第四个盒子,输出132。

之后的排列也是如此得到的,便不赘述了。


由这个全排列的例子,我们便可以了解到深度优先搜索的基本模型了。理解dfs的关键在于解决“当下该如何做”,至于下一步,则与当下是一样的。 比如全排列这道题,dfs函数就是解决面对第step个盒子时如何做,方法就是把每一种可能都尝试一遍,而step+1个盒子的解决办法是一样的,所以才使用递归。

基本模型:

void dfs(int step)
{
    判断边界
    尝试每一种可能 for(int i=1;i<=n;++i)
    {
        继续下一步 dfs(step+1);
    }
    return;
}

题目2:解救小哈

迷宫由m行n列单元格组成(m,n均小于等于50),每个单元格要么是空地要么是障碍物,你的任务是帮助小哼找到一条从迷宫起点通往小哈所在位置的最短路径。

#include
using namespace std;
int m, n, endx, endy, min = 99999999;   //地图为m行n列  (endx,endy)为目标坐标
int a[55][55], book[55][55];    //a为地图二维数组(1为障碍物),book为现在所在坐标二维数组
void dfs(int x, int y, int step)
{
	int next[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };   //右->下—>左->上
	int nx, ny;
	if (x == endx && y == endy)   //递归出口
	{
		if (step < min)
			min = step;
		return;
	}
	for (int i = 0; i != 4; ++i)   //尝试四种走法
	{
		nx = x + next[i][0];
		ny = y + next[i][1];
		if (nx<1 || nx>m || ny<1 || ny>n)
			continue;
		if (a[nx][ny] == 0 && book[nx][ny] == 0)  //下一个点不是障碍物且没有走过
		{
			book[nx][ny] = 1;
			dfs(nx, ny, step + 1);
			book[nx][ny] = 0;
		}
	}
	return;
}
int main()
{
	freopen("C:\\Users\\statue\\OneDrive\\桌面\\test.txt", "r", stdin);
	int startx, starty;
	cin >> m >> n;
	for (int i = 1; i<= m; ++i)
		for (int j = 1; j <= n; ++j)
			cin >> a[i][j];
	cin >> startx >> starty >> endx >> endy;
	book[startx][starty] = 1;  //避免重复走
	dfs(startx, startx, 0);
	cout << min << endl;
	freopen("CON", "r", stdin);
	system("pause");
}

其实方法都是一样的,但我在自己敲的过程中有个bug找了很久。就是在最上方定义了全局变量m,n,但在main函数中我又习惯性的定义了m,n,这时局部变量m和n便覆盖了全局变量m和n 导致输出结果永远都是99999999。。

另外,作者定义的地图起点默认是(1,1) 当然我们也可以用(0,0)只需注意修改下循环和if条件的判断和起始坐标的输入即可。


解决解救小哈这一问题还可以用别的方法。这时也就引出了广度优先搜索。

二、广度优先搜索Breath First Search)

广度优先搜索是用队列模拟实现的。我们最初站在原点,按右->下->左->上 一轮往周边搜索,若满足条件则让其入队,步数是在所站位置上的步数加一,搜索完后,将所站的点去掉,因为已经无用了,再往周边搜索,以此类推,直至找到目标。

这次我们便将原点设为(0,0)

#include
using namespace std;
int a[55][55], book[55][55];
struct note
{
	int x;
	int y;
	int step;
}que[2505];
int main()
{
	int m, n, startx, starty, endx, endy, nx, ny, flag;
	cin >> m >> n;                   //输入数据
	for (int i = 0; i != m; ++i)
		for (int j = 0; j != n; ++j)
			cin >> a[i][j];
	cin >> startx >> starty >> endx >> endy;
	int head = 1, tail = 1;
	que[tail].x = startx;          //在队列尾部添加初值
	que[tail].y = starty;
	que[tail].step = 0;
	++tail;
	flag = 0;       //判断是否找到的标记变量
	book[startx][starty] = 1;
	int next[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
	while (head < tail)
	{
		for (int i = 0; i != 4; ++i)
		{
			nx = que[head].x + next[i][0];
			ny = que[head].y + next[i][1];
			if (nx < 0 || nx >= m || ny < 0 || ny >= n)
				continue;
			if (a[nx][ny] == 0 && book[nx][ny] == 0)
			{
				book[nx][ny] = 1;
				que[tail].x = nx;
				que[tail].y = ny;
				que[tail].step = que[head].step + 1;
				++tail;
			}
			if (nx == endx && nx == endy)
			{
				flag = 1;
				break;
			}
		}
		if (flag == 1)
			break;
		++head;   //这样才能往前推进,进行下一次拓展
	}
	cout << que[tail - 1].step << endl;
	system("pause");
}

题目3:再解炸弹人

在第三章枚举中,炸弹人程序存在一些问题,就是求出的结果不是小哼能走过去的最佳点,而仅仅是数量最佳。要解决这一问题就需要搜索。(注意:题目已经修改了!第6行第11列的障碍物要改成空地!)

法一(BFS):

#include
using namespace std;
struct  note
{
	int x;
	int y;
};
char a[25][25], book[25][25];
int getnum(int i, int j)
{
	int x, y, sum = 0;
	x = i; y = j;
	while (a[x][y] != '#')    //向上统计
	{
		if (a[x][y] == 'G')
			sum++;
		x--;
	}
	x = i; y = j;
	while (a[x][y] != '#')    //向下统计
	{
		if (a[x][y] == 'G')
			sum++;
		x++;
	}
	x = i; y = j;
	while (a[x][y] != '#')    //向左统计
	{
		if (a[x][y] == 'G')
			sum++;
		y--;
	}
	x = i; y = j;
	while (a[x][y] != '#')    //向右统计
	{
		if (a[x][y] == 'G')
			sum++;
		y++;
	}
	return sum;
}
int main()
{
	struct note que[405];
	int head = 1, tail = 1;
	int m, n, sum, max=0, startx, starty, nx, ny, max_x, max_y;
	int next[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
	cin >> m >> n >> startx >> starty;
	for (int i = 0; i != m; ++i)
		for (int j = 0; j != n; ++j)
			cin >> a[i][j];
	que[tail].x = startx;
	que[tail].y = starty;
	++tail;
	book[startx][starty] = 1;
	max = getnum(startx, starty);
	max_x = startx;
	max_y = starty;
	while (head < tail)
	{
		for (int i = 0; i != 4; ++i)
		{
			nx = que[head].x + next[i][0];
			ny = que[head].y + next[i][1];
			if (nx<0 || nx>m - 1 || ny<0 || ny>n - 1)
				continue;
			if (a[nx][ny] == '.' && book[nx][ny] == 0)
			{
				book[nx][ny] = 1;
				que[tail].x = nx;
				que[tail].y = ny;
				tail++;
				sum = getnum(nx, ny);
				if (sum > max)
				{
					max = sum;
					max_x = nx;
					max_y = ny;
				}
			}
		}
		head++;
	}
	cout << "将炸弹放在(" << max_x << "," << max_y << ")可以消灭" << max << "个敌人" << endl;
	system("pause");
}

法二(DFS)

#include
using namespace std;
char a[25][25];
int book[25][25], max, max_x, max_y, nx, ny, m, n;
int getnum(int i, int j)
{
	int x, y, sum = 0;
	x = i; y = j;
	while (a[x][y] != '#')
	{
		if (a[x][y] == 'G')
			sum++;
		x--;
	}
	x = i; y = j;
	while (a[x][y] != '#')
	{
		if (a[x][y] == 'G')
			sum++;
		x++;
	}
	x = i; y = j;
	while (a[x][y] != '#')
	{
		if (a[x][y] == 'G')
			sum++;
		y--;
	}
	x = i; y = j;
	while (a[x][y] != '#')
	{
		if (a[x][y] == 'G')
			sum++;
		y++;
	}
	return sum;
}
void dfs(int x, int y)
{
	int next[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
	int sum;
	sum = getnum(x, y);
	if (sum > max)
	{
		max = sum;
		max_x = x;
		max_y = y;
	}
	for (int i = 0; i != 4; ++i)
	{
		nx = x + next[i][0];
		ny = y + next[i][1];
		if (nx<0 || nx>m - 1 || ny<0 || ny>n - 1)
			continue;
		if (a[nx][ny] == '.'&&book[nx][ny] == 0)
		{
			book[nx][ny] = 1;
			dfs(nx,ny);
			//book[nx][ny]=0;   不需要这步,因为只要遍历到就行,不管顺序
		}
	}
	return;
}
int main()
{
	int startx, starty;
	cin >> m >> n >> startx >> starty;
	for (int i = 0; i != m; ++i)
		for (int j = 0; j != n; ++j)
			cin >> a[i][j];
	dfs(startx, starty);
	cout << max_x << " " << max_y << " " << max << endl;
	system("pause");
}

题目4:宝岛探险

法一(BFS): 利用了C++的queue (注意queue的struct构造:q.push({x,y}))

#include
#include
using namespace std;
int a[15][15];
int book[15][15];
struct note
{
	int x;
	int y;
	note() = default;
	note(int i, int j) :x(i), y(j) {};
}que[100];
int main()
{
	int m, n, startx, starty, nx, ny, sum = 0;
	queue q;
	int next[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
	cin >> m >> n >> startx >> starty;
	book[startx][starty] = 1;
	for (int i = 1; i <= m; ++i)
		for (int j = 1; j <= n; ++j)
			cin >> a[i][j];
	q.push({ startx,starty });
	sum++;
	while (!q.empty())
	{
		for (int i = 0; i != 4; ++i)
		{
			nx = q.front().x + next[i][0];
			ny = q.front().y + next[i][1];
			if (nx<1 || nx>m || ny<1 || ny>n)
				continue;
			if (a[nx][ny] > 0 && book[nx][ny] == 0)
			{
				book[nx][ny] = 1;
				q.push({ nx,ny });
				sum++;
			}
		}
		q.pop();
	}
	cout << sum << endl;
	system("pause");
}

法二(DFS):

#include
using namespace std;
int a[15][15], book[15][15];
int m, n, sum = 0;
void dfs(int x, int y)
{
	int next[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
	int nx, ny;
	book[x][y] = 1;
	sum++;
	for (int i = 0; i != 4; ++i)
	{
		nx = x + next[i][0];
		ny = y + next[i][1];
		if (nx<1 || nx>m || ny<1 || ny>n)
			continue;
		if (a[nx][ny] > 0 && book[nx][ny] == 0)
			dfs(nx, ny);
	}
	return;
}
int main()
{
	int startx, starty, nx, ny;
	cin >> m >> n >> startx >> starty;
	for (int i = 1; i <= m; ++i)
		for (int j = 1; j <= n; ++j)
			cin >> a[i][j];
	dfs(startx, starty);
	cout << sum << endl;

}

看完后觉得搜索蛮强大的hh

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