我与深搜的第三次

 ok,我的作业博客在后面,我先把我的学习心得放在前面

 ok,我的作业博客在后面,我先把我的学习心得放在前面

 ok,我的作业博客在后面,我先把我的学习心得放在前面

深度搜索可以说是循环与递归的完美结合,它注重的是我们的每一步到了下一步应该怎么走。

一开始看到第一个例题的时候,懵了很长时间。

思考了很长时间,最后是明白了。

一个简单的例题:

给你一个原始的数,输出它的所有的逆序数。

我们应该这样去看:

如果给出的这个数有n个位数

那么在脑海里面想象出一个图表,每一行都是”12..n“

之后有n行。

然后从第一行开始任意选择一个起点,然后下一步呢?就是到下一行再找一个数字。那个数字不能和之前的数字重复,之后重复地找下去,知道再第n行输出我们的寻找的结果,就是这样。

那么这样的话,只是找到了一种情况。下面就是再从头开始,再找一个新的第一行的数字。

然后还是重复。

这个过程就体现出了很明显的递归特点。但是其实不仅仅是递归。

我们来分析一下

我们先选择第一行开始。这里需要用for循环。因为第一行的各个数字都要遍历的。

这个循环会给出我们一个数字,当作我们组合数的其中一个情况的第一个数。之后,为了避免重复,我们需要再建立以数组来防止重复。可以建立一个有10个元素的book数组,然后呢,第一个元素不管,第一个元素的存在只是为了让其他元素的下标与其对应的数字是相同的。同时先让所有的元素的值为1.之后,一旦有那个数字被选择了,相应的book数组里面的下标为这个数的元素的值为0,之后到了下一行作为选择的依据。

ok第一行选完了,在选好第一行之后呢,首先对这个数字进行标记(用book来实现),再建立一个数组(也可以是栈)来存放我们选好的第一个数字。这个存放数字的数组就叫”ans”吧。之后呢,我们要进入到下一行。这时候怎么办呢?我们可以用函数递归的方法。因为对于每一行(除了最后一行),我们的操作都是相同的。那就是先选,在判断,在入栈,继续进入下一行。就这样。所以说很适合递归。我们被递归的函数的任务就是,对于任何非最后一行的行,进行选择、判断、入栈、进入下一行的操作。所以说很适合递归。而且这个队规函数的参数应该至少有行数。所以说,在第一行的选择、判断、入栈结束之后,我们就应该进行递归,到第二行进行同样的操作。

但是这时候你可能回想,那么不断地进行下去,到最后不就只是选出来一种情况吗?假设就3行,分析一下,一开始,第一行应该选择1,自己调用自己,参数+1,之后到了第二行,第二行选择2,之后输出我们的答案“12“,之后呢?不要忘了,这个是递归啊!这相当于什么?第三次调用完成了,之后返回第二次调用。不要忘了第二次调用是在一个for循环里面对自己进行调用的。返回之后,第二次调用继续进行它的for循环。之后又找到下一个数字,再次进行第三次调用。以此类推,在第二次调用的for循环完成之后,这第二次调用就完成了。然后回到第一次调用。我们继续进行第一次调用的for循环,我们第一次调用的for循环会给我们第二个数:2。但是,这时候聪明的你肯定可以发现,我们的2在第二次调用的时候,已经被提前标记过了!这时候,为了防止这种情况的发生,我们就应该在么一次for循环引发的调用完成之后,回到for循环之后,这一节循环还不能结束,我们还应该让刚刚被我们选过的数字的标记进行抹除。这样子不会影响到我们的程序运行。因为是这样的,我们的for循环不会吃回头草,并且对于for循环所引发的每一次调用,它们只需要知道引发它们自己的那个for循环所选择的是什么数字就好了。我们把标记抹除放在调用之后,不回影响到这个for循环所引发的调用。所以说,我们的程序也就不会受到影响。同时在调用之后抹除标记也是必要的。

也就是说,我们选出一个情况之后(也就是在所有行里面都选出数字以后),我们的调用开始一个个返回,但是,最近的调用刚返回去,就碰到了for循环(我们假设这个新的循环小节所选出来数字是满足条件的),自己就又被调用了一次。以此类推。等到这个最近第一调用的前一次调用中的for循环终于循环完毕了,最近的一次终于退休了,这时候就该第二进的调用返回了。但是它遇到了和第一次调用一样的问题:他也遇到了for循环。之后继续调用最近一次的调用。(可怜的最近一次的调用)。之后一直重复下去。等到哪天终于,第一次调用的最后一个for循环小节里的调用也成功的返回了。这才算完成 。

这就是深度搜索。

用循环来托缓递归的进度,用循环来一次又一次地掀起递归地热潮。

#include <iostream>
#include <iostream>
using namespace std;

int jg[10];
int n;		// 代表个数
void dfs(int step);
int ans[10];
int main()
{
	for (int& x : jg)
	{
		x = 1;
	}
	string a;
	cin >> a;
	n = a.size();
	//	n代表了是几位数
	dfs(1);
	return 0;
}
void dfs(int step)
{
	
	if (step == n+1)  //	这代表了最后一次调用地结果
	{
		for (int i = 1; i <= n; i += 1)
		{
			cout << ans[i];
		}
		cout << '\n';
		return;
	}
	for (int i = 1; i <= n; i += 1)
	{
		if (jg[i])
		{
			jg[i] = 0;
			ans[step] = i;
			dfs(step + 1);
			jg[i] = 1;   //	为了后面的行不影响前面的行
		}
	}
	return;
}

 

 上一次我经历了我与深搜的第一次。理解了之后感觉很有成就感。

但是,那仅仅是一部分。我对深搜还没有完全的理解。那么就下来,就再来一次。

(听说week4的第一题要用广搜而不是深搜,但是我打算先学会深搜。毕竟都是学习嘛。)

(深搜吃透了,广搜其实就好理解了)

那么先把这次研究的例题搞上去:

问题:GeoSurvComp地质调查公司负责探测地下石油储藏。 GeoSurvComp现在在一块矩形区域探测石油,并把这个大区域分成了很多小块。他们通过专业设备,来分析每个小块中是否蕴藏石油。如果这些蕴藏石油的小方格相邻,那么他们被认为是同一油藏的一部分。在这块矩形区域,可能有很多油藏。你的任务是确定有多少不同的油藏。

input: 输入可能有多个矩形区域(即可能有多组测试)。每个矩形区域的起始行包含m和n,表示行和列的数量,

1<=n,m<=100,如果m =0表示输入的结束,接下来是n行,每行m个字符。每个字符对应一个小方格,并且要么是’*’,代表没有油,要么是’@’,表示有油。

output: 对于每一个矩形区域,输出油藏的数量。两个小方格是相邻的,当且仅当他们水平或者垂直或者对角线相邻(即8个方向)。

 这个问题与之前的那个稍微复杂了一些。但是也不成问题。

上一个问题中,我们达到下一行之后,要先开始一个for循环。去遍历我们在这一行里面的所有情况。

我们抽象一下。再上一个题里面。我们每走一步就是进到下一个行。也就是说,我们的“步”就是行。

这个题呢?我们的每一步,就成了每一格油田了,我们的下一步是下一格油田。

然后呢?每一步之间是什么联系的呢?

上一个题目,当前步要先进行数字的选择才能进到下一步。这里也一样,这里是先要进行方向的选择,才能进到下一步。

二者有什么不同呢?

他们其实没什么不同。上一个的第一次调用是去找所有的组合,这次一的调用失去开辟每一个方向。其实二者都是不断地产生分支。但是这分支绝对不是同时产生的!如果用for循环所给的‘i“来表示我们的分支的话:

第一次:11111111111

第二次:11111111112

第三次:11111111121

就是这样。

原因就在于多个递归的出栈顺序是先进先出的。

okk

那么下面分析一下代码。

分析的话,我就放在代码里面的注释里了!

                                                            

#include <iostream>
using namespace std;
int m, n;
int sum = 0;
int judge(int x, int y);
int dfs(int x, int y);
int a[101][101];
int v[9][2] = { {0,0},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1.0},{-1,1} };
int main()
{
	cin >> m >> n;
	for (int i = 1; i <= m; i += 1)
	{
		for (int x = 1; x <= n; i += 1)
		{
			cin >> a[m][n];
		}
	}
	//	m行,n列
	for (int x = 1; x <= m; x += 1)
	{
		for (int y = 1; y <= n; y += 1)
		{
			if (dfs(x, y))
			{
				sum += 1;
			}
		}
	}
	cout << sum;
	return 0;
}
int judge(int x, int y)
{
	if (a[x][y] == '@'&&x<=100&&y<100)  //  这里用来形成油田的边界
	{
		
		return 1;
	}
	else
	{
		return 0;
	}
}
int dfs(int x, int y)
{
	if (judge(x, y))  //  注意,我们的深搜的程序必须在这个if里面,因为我们只要碰到不是油田的,就停止这一个分支
	{
		a[x][y] = '*';   // 以此来标记一个已经被统计过的油田
		//	上面这一步很重要这种格子地图遍历的关键
		//  走迷宫也是,我们走过了就不能再走了,这时候就要标记。
		//  大家想一下,为什么迷宫题走过了就不能再走了?原因在于避免重复统计
		//	下面想一下怎么去解决遍历方向的问题
		//   一共八个方向,很自然的就像想到,可以用一个for循环实现。也就是i从1到8;
		//	然后我想到了矩阵。刚学的。
		//  我们建立一个二维数组,代表了向8个方向走的话,x与y的变化
		//	就叫这个数组v吧。
		for (int i = 1; i <= 8; i += 1)
		{
			//  这时候我遇到了一个问题,那就是这个新的坐标应该定义在什么地方?
			//  想一想,这个新的坐标只有接下来的调用需要用,所以在for循环小节里面定义就好
			int nx, ny;
			nx = x + v[i][0];
			ny = y + v[i][1];
			//	选择之后进行递归的调用
			//  不要脸地把任务交给无数个dfs
			dfs(nx, ny);
			return 1;  //  这就是深搜地精华!这是程序一脚踏进去深不见底的调用以后,终于爬出来以后的样子
		}
	}
	return 0;  //  就像前面说的一样,如果这个起步的点本身就不是油田的话,那就直接理都不理,sum也不会+1
}

okk

就到这里啦 

就这样

我真的是太有才了,点个赞吧

题目描述

给定一个 N \times MN×M 方格的迷宫,迷宫里有 TT 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

输入格式

第一行为三个正整数 N,M,TN,M,T,分别表示迷宫的长宽和障碍总数。

第二行为四个正整数 SX,SY,FX,FYSX,SY,FX,FY,SX,SYSX,SY 代表起点坐标,FX,FYFX,FY 代表终点坐标。

接下来 TT 行,每行两个正整数,表示障碍点的坐标。

输出格式

输出从起点坐标到终点坐标的方案总数。

输入输出样例

 很简单。看过我之前的分析,就知道这道题对我来说有多简单了!

是这样的,这道理的关键在于”回溯“

也就是说,应该在试探到底之后,我们就应该回溯,这时候要记得把走过的路去除标记。因为接下来要试探的时另外一种新的情况,我们之前的试探不能对这新一轮的试探产生影响。

所以要清零。ok,上代码!

#include <iostream>
using namespace std;
int sx, sy, fx, fy;
int sum = 0;
int n, m;
int a[5][5];
int v[4][2] = { {0,1},{0,-1},{-1,0},{1,0} };
void dfs(int x, int y);
int main()
{
	cin >> n >> m;//n是长
	int t;
	cin >> t;
	

	cin >> sx >> sy >> fx >> fy;
	for (int i = 0; i < t; i += 1)
	{
		int x, y;
		cin >> x >> y;
		a[x][y] = 1;
	}
	dfs(sx, sy);
	cout << sum;
	return 0;
}
void dfs(int x, int y)
{
	if (x > n || y > m||x<1||y<1)
	{
		return;
	}
	if (a[x][y] == 1)
	{
		return;
	}
	if (x == fx && y == fy)
	{
		sum += 1;
		return;
	}
	a[x][y] = 1;
	for (int i = 0; i < 4; i += 1)
	{
		
		int nx, ny;
		nx = x + v[i][0];
		ny = y + v[i][1];
		dfs(nx, ny);
		
	}
	a[x][y] = 0;
}

 ok,那麽接下来进行就是下一道题:

题目描述

有一个 n \times mn×m 的棋盘,在某个点 (x, y)(x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。

输入格式

输入只有一行四个整数,分别为 n, m, x, yn,m,x,y。

输出格式

一个 n \times mn×m 的矩阵,代表马到达某个点最少要走几步(不能到达则输出 -1−1)。

这个让我愣了一会儿。

发现要用广搜。我感觉广搜要比深搜好理解(看代码的话)

广搜的关键在于,我们要建立一个平台。来存放当前的每一种状态。

之后,把每一个状态换成它的所有分支状态。

这就是关键。

然后回到这道题。其实并不是很难。(如果你知道🐎怎么走的话)

关键在于, 要建立两个二维数组,一个和用来记录步数,一个用来进行步伐。

那就上代码吧!

#include <iostream>
#include <queue>
using namespace std;
int n, m, x, y;
int dx[8] = { -1,-2,-2,-1,1,2,2,1 };
int dy[8] = { 2,1,-1,-2,2,1,-1,-2 };
int sum[501][501];
bool rem[501][501];
queue<pair<int, int> > q;
int main()
{
	cin >> n >> m >> x >> y;
	memset(sum, -1, sizeof(sum));
	memset(rem, 0, sizeof(rem));
	sum[x][y] = 0;
	rem[x][y] = 1;
	q.push(make_pair(x, y));
	while (!(q.empty()))
	{
		int nx = q.front().first;
		int ny = q.front().second;
		q.pop();
		for (int i = 0; i < 8; i += 1)
		{
			int xx = nx + dx[i];
			int yy = ny + dy[i];
			if (xx<1 || xx>n || yy<1 || yy>m || rem[xx][yy])
			{
				continue;
			}
			rem[xx][yy] = 1;
			q.push(make_pair(xx, yy));
			sum[xx][yy] = sum[nx][ny] + 1;
		}
	}
	for (int i = 1; i < n + 1; i += 1)
	{
		for (int j = 1; j < m+1; j += 1)
		{
			cout << sum[i][j] << ' ';
		}
		cout << endl;
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值