算法竞赛入门-八皇后问题

在棋盘上放置8 个皇后,使得它们互不攻击, 此时每个皇后的攻击范围为同行同列和对角线,要求找出所有解

【分析】

思路一:把问题转化为“从64 个格子中选一个子集”,使得“子集中恰好有8 个格子,

且任意两个选出的格子都不在同一行、同一列或同一个对角线上” 。这是子集枚举问题,不

是一个好的模型。

思路二: 把问题转化为“从64 个格子中选8 个格子”,这是组合生成问题。比思路一好,

但是仍然不是很好。

思路三:由分析可得,恰好每行每列各放置一个皇后。如果用C[x] 表示第x 行皇后的

列编号,则问题变成了全排列生成问题。

提示7-4 :在编写递归枚举程序之前,需要深入分析问题,对模型精雕细琢。一般还应

对解答树的结点数有一个粗略的估计,作为评价模型的重要依据,

当把问题分成若干步骤并递归求解时,如果当前步骤没有合法选择,则函数

将返回上一级递归调用, 这种现象称为回溯。正是因为这个原因, 递归枚举算法常称为回溯

法,它的应用十分普遍。

在主程序中读入n,并为tot 清0,然后调用search(0) ,即可得到解的个数tot 。当n=8,则tot=92, 状态空间结点数nc=2057。完整的程序如下:

 

#include<stdio.h>
#include<iostream>
using namespace std;
int  n, tot = 0, nc = 0;
int C[50];
int vis[3][50];
void search(int cur)
{
	nc++;
	int i, j;
	if (cur == n)//递归边界,只要走到了这里,所有皇后必然不冲突
	{
		tot++;
		for (int k = 0; k < n; k++)
		{
			cout << C[k] << " ";
		}
		cout << endl;
	}
	else for (i = 0;i < n; i++)
		{
			int ok = 1;
			C[cur] = i;//尝试把cur行的皇后放在第i列(C[cur]表示行的值,cur表示行。 cur - C[cur]表示对角线的值)
			for(j=0;j<cur;j++)//检查是否和前面的皇后冲突
				if (C[cur] == C[j] || cur - C[cur] == j - C[j] || cur + C[cur] == j + C[j])//列以及正负对角线的值是否相等来判断是否冲突
				{
					ok = 0; break;
				}
			if (ok)
				search(cur + 1);//如果合法,则继续递归
		}
}
int main()
{
	cin >> n;
	memset(vis, 0, sizeof(vis));
	search(0);
	cout << tot << endl;
	cout << nc << endl;
	system("pause");
	return 0;
}

既然是逐行放置的, 则皇后肯定不会横向攻击, 因此只需检查是否纵向和斜向攻

击即可。条件cur-C[cur] == j-C[j] || cur+C[cur]== j+C[j] 用来判断皇后(cur,C[cur])

和(j,C[j]) 是否在同一条对角线上。其原理可以用图7-26 说明。


(a) 格子(x,y) 的y-x 值标识了主对角线(b) 格子(x,y) 的x+y值标识了副对角线

图7-6 棋盘中的对角线标识

上面的程序还可改进: 利用二维数组vist[2][] 直接判断当前尝试的皇后所在的列和两

个对角线是否已有其他皇后。注意到主对角线标识y-x 可能为负,存取时要加上n。完整的

程序如下:

#include<stdio.h>
#include<iostream>
using namespace std;

int  n, tot = 0, nc = 0;
int C[50];
int vis[3][50];

void search(int cur)
{
	int i, j;
	nc++;
	if (cur == n) tot++;
	else for (i = 0; i < n; i++)
		{
			if (!vis[0][i] && !vis[1][cur + i] && !vis[2][cur - i + n])//利用二维数组直接判断
			{
				C[cur] = i;//如果不需要打印解,整个C数组可以省略
				vis[0][i] = vis[1][cur + i] = vis[2][cur - i + n] = 1;//修改全局变量
				search(cur + 1);
				vis[0][i] = vis[1][cur + i] = vis[2][cur - i + n] = 0;//切记一定要改回来(回溯 还原状态)
			}		
		}
}
/*上面的程序关键的地方是vis 数组的使用。vis 数组表示已经放置的皇后占据了哪些列、
主对角线和副对角线(行列的标号是可以动的,就看自己是怎样定义的了)。(这三个状态表示确定是否有重叠现象)
将来放置的皇后不应该修改这些值。一般地, 如果要回溯法中修改了辅助的全局变量,则一定要及进把它们恢复原状
(除非故意保留修改) 。另外,千万不要忘记在调试之前把vis 数组清空。*/
int main()
{
	cin >> n;
	memset(vis, 0, sizeof(vis));
	search(0);
	cout << tot << endl;
	cout << nc << endl;
	system("pause");
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值