一.问题分析
N×N的棋盘上有N个皇后,皇后的攻击范围是同一行,同一列和对角线.
N个皇后要如何安全排列?
在一个二维数组中,一行一行地看,观察第 i 行的第 i 个皇后能放在第几列.也就是说,默认每个皇后不可能出现在同一行.
↑显约束.
所以,在判断第t个皇后时,只需判断是否
a.列冲突:x[t]==x[i] i的范围是1~t-1
b.对角线冲突:t-i==abs(x[t]-x[i]) i的范围是1~t-1
↑隐约束
二.代码实现
分为三个部分
1.全局变量区
2.约束函数
3.递归函数
4.调用递归函数
1.全局变量
#include<cmath>
const int M = 20;
int n;//表示n个皇后
int x[M];//例如:x[2]=5表示第2行的皇后在第5列,数组稍宽些会少许多麻烦
int countn=0;//n皇后解的个数
2.约束函数
//形参t表示第t个皇后能否与前0~t-1个皇后不冲突
bool Place(int t)//第t个皇后能否放在第i个位置
{
bool flag = true;
for (int i = 1; i < t; i++)//观察是否与第1~t-1个皇后冲突
{
//在同一列 或 对角线 上
if (x[t] == x[i] || t - i == fabs(x[t] - x[i]))
{
flag = false;
break;
}
}
return flag;
}
3.递归函数
一个形式上很简洁的递归.
尚未遍历到第n行时,遍历每一列,查找允许的安全位置
能够遍历到第n行,说明得出一个解,打印出来
"数组是从0开始的" 这一点有时候很棘手,不如在设置数组时多设置一单位长度.统一从1开始
void queen(int t) //当前遍历的第t个皇后
{
if (t > n2)
{
countn++;//表示找到一个解
for (int i = 1; i <= n2; i++)
{
cout << x[i] << "\t";
}
cout << endl;
cout << "------------" << endl;
}
else
{
for (int i = 0; i < n2; i++)//分别判断n个分支
{
l[t] = i;//※(第一次的bug)若不添加该语句,x[]始终处于刚刚初始化的状态即memset(x,0,sixeof(x)),程序无法运行出结果
if (Place(t))
{
queen(t + 1);
}
}
}
}
4.main()
cout << "请输入1~20以内的数作为皇后个数:";
cin >> n2;//Queen.h定义的变量,也可以像函数那样被main()调用
if (n2 > 20)
{
cout << "输入值偏大,请重新输入:";
cin >> n2;
}
queen(1);
cout << "解的个数: " << countn;
三.拓展优化
回溯法的优化,就是要减小解空间的大小,精简m叉树.具体来说,就是将隐约束 变成 显约束.
在以上代码中,0~n-1的每一个分支都要判断到并递归,这显然是极大的浪费.
通过隐约束可知,不可能是同一列,可以设置一个bool live[n]数组用于判断之前元素占的是哪一列.
在for()里嵌一个if()判断来减少循环次数.
最后效果如图: