算法之递归回溯(三)

前两篇的内容重点放在的功能和作用上递归回溯的过程
这一篇我们聊一聊回溯法在迷宫棋盘类问题中的应用吧。一般来说,这种迷宫棋盘类的问题都是使用二维数组来求解。但又是也有例外。这是一个典型的例外——八皇后问题。那么,关于这类问题,我们在什么情况下选择一维数组,什么情况下选择二维数组呢?这跟数组的功能有关系。一般来说,一维数组中的元素最多可以表示两层含义。第一层由数组中的元素提供,第二层由数组的下标提供。比如说,对于数组int arr[4] = {1,2,3,4}来说,arr[3]得到的4可以表示一层含义,其中的下标3也可以表示一层含义。而二维数组则能表达三个含义——两个下标和一个值。接下来我们来查看八皇后问题。

在8*8的国际象棋棋盘上放下八个皇后。要求八个皇后任意两个不能在同一行,同一列,同一对角线上。求一共有多少种摆法,每一种摆法又是如何摆放的?

从题干中我们了解到棋盘是8*8的,因此大家第一反应是创建一个二维数组来存放。这是可以的。但我们也了解到,二维数组的计算量远远大于一维数组,我们会思考能否使用一维数组来求解呢?我们知道,一个皇后在一行内只会放在一个位置——两个皇后不能在同一行。那么,也就是说,对于一行中的皇后我们只需要知道列就可以了。这样的话,我们可以使用一维数组来表示皇后的位置——下标表示行数,数组值表示列数。例如:arr[3] = 4的意思是第4行,第5列的位置有一个皇后。
现在我们知道如何存放皇后的信息了。于是我们可以从第一行开始摆皇后,第一行放好之后,会接着放第二行…依次进行,直到第8行的皇后也放好即可。那对于每一行的皇后该放在什么位置呢?我们并不了解,我们选择将每一个位置都尝试一遍,如果满足条件,则保留这个位置,否则换成下一个位置。
我们尝试使用伪代码的方式描述这个题解。

int arr[8] = {};	//存放皇后信息,arr[0] 存放的是第0行,第arr[0]列的皇后
void f(int row)	//参数表示放到了第row行
{
	if(row == 8)	//8行都已经放完
	{
		//将完整的摆法输出
		//使用0表示没有皇后,1表示皇后
		for(int i = 0;i < 8; ++i)
		{
			for(int j = 0; j < 8; ++j)
				if(j == arr[i])
					cout << 1 << " ";
				else
					cout << 0 << " ":
			cout << endl;
		}
		return;
	}
	for(int i = 0;i < 8; ++i)	//皇后可以放在0~7的任何一列
	{
		arr[row] = i;	//第row行第i列的位置放上皇后
		if(满足放皇后的条件)
			f(row + 1);	//下一行
	}
}

在程序中,我们使用循环从07的每一列都尝试放一遍皇后,如果满足,则开始放下一行的皇后。如果不满足条件,循环会继续将i的值覆盖在arr[row]的位置,也就相当于更换了列数。那么关键的判断满足皇后的条件该如何描述呢?我们需要从下标寻找规律——一般二维数组的规律都是从下标寻找
在这里插入图片描述

题目中要求,任意两个皇后不能在同一行,同一列,同一对角线。也就是如图中皇后(红色)位置周围的绿色、蓝色、粉红色、黄色的位置都是不能放置皇后的。那么这该如何使用程序表达呢?我们一个一个查看。
1、同一行。我们使用一维数组来表示八皇后的信息,其中 下标表示行, arr[row]表示列。而一维数组中 arr[row]位置只能存放一个元素,因此,是不可能造成同一行有两个皇后的。除非 arr[row]的位置可以存放两个元素。
2、同一列。我们了解到 arr[row]表示列数。那么也就要求 arr[0]、arr[1]、....arr[row]之间不能相同。这个判断是比较简单的。我们尝试描述一下。
for(int i = 0; i < row; ++i)
	if(arr[i] == arr[row])
		return false;	//列相同,表明不能放置
return true;	//列没有相同,表明可以放置
这段代码中需要注意的是,只有所有的列都满足条件,才能返回 true表明能够放置,而不是满足了一个列数不相同就返回 true
3、同一对角线。对角线其实包含两条,一条是从左上角到右下角的。如图:
在这里插入图片描述

那么在这个图中,如何使用程序表达在同一个对角线上呢?我们尝试找到对应的下标。

位置行下标列下标
皇后(红色)43
a21
b76

从下标关系来看,我们了解到,行下标列下标的相等的两个位置,它们在同一对角线上。4-3等于2-1

for(int i = 0;i < row; ++i)
	if(i - arr[i] == row - arr[row])	//从左上角到右下角的对角线
		return false;
return true;
这里值得注意的是,程序只需要判断到 row行即可,毕竟大于 row的行还没有放置皇后呢。
4、同一对角线。从左下角到右上角的对角线。如图:
在这里插入图片描述
有了上一个图的经验,我们同样列一个表格来表示行下标和列下标的规律吧。
位置行下标列下标
皇后(红色)21
a03
b30

这个规律从ab之间就很好发现了,a的行下标和列下标之和与b的行下标和列下标之和相等。1+2也等于0+3。因此我们可以这样描述:

for(int i = 0;i < row; ++i)
	if(i + arr[i] == row + arr[row])	//在同一对角线,从左下角到右上角
		return false;	
return true;

终于,我们将四种情况都说明清楚了,我们可以使用一个函数表示放置皇后是否满足条件。

bool isOk(int row)
{
	for(int i = 0;i < row; ++i)
	{
		if(arr[i] == arr[row])	//列相同
			return false;
		if(i - arr[i] == row - arr[row])	//从左上角到右下角
			return false;
		if(i + arr[i] == row + arr[row])	//从左下角到右上角
			return false;
	}
	return true;	
}

通过这样的方式,八皇后摆放的问题基本上就已经解决了。我们使用递归回溯的方式,将每一个位置尝试一遍,如果位置不冲突,则确定放置,否则,我们换到下一个位置。这样保证了程序的有效性。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值