在棋盘上放置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;
}