n皇后问题
题目:
在n * n的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之在同一行、同一列、同一斜线上的棋子。设计算法在n * n的期盼上放置n个皇后,使其彼此不受攻击。
思路分析:
核心思路: 以行为主导,去考虑每一行的皇后该放在哪一列,才能获得可行解。
n皇后问题解的形式: n元组 {x1, x2, … , xi , …, xn}, 分量xi表示第i行皇后放置的列位置(即i行xi列)
解空间组织结构: m叉树(m = n)
显约束(对解分量的取值范围的限定): 不同列(取值1 ~ n)
隐约束(能否得到问题的可行解或最优解做出的约束):
1)约束条件: 约束条件是对可行解做出的约束,在这里指第t个皇后的位置不能和前t - 1 个皇后同列、同斜线。
2)限界条件: 限界条件是对最优解做出的约束,该问题不是求最优值,只是求可行方案,因此不需要限界条件。
具体搜索过程:
从根开始,以深度优先搜索的方式进行搜索。根节点是活结点,并且以该活结点为当前扩展结点。当前扩展结点沿纵深方向扩展出一个新结点,判断该结点是否满足约束条件。如果满足,则该新节点变成活结点,并且成为当前的扩展结点,继续向深层搜索;如果不满足约束条件,则该新结点变成死结点,然后换到该新结点的兄弟结点继续搜索,如果新结点没有兄弟结点,或其兄弟结点已经全部搜索完毕,则当前扩展结点变成死结点,搜索回溯到当前扩展结点的父结点继续进行。搜索过程直到根节点变成死结点为止,即搜索完毕。
实现约束条件的约数函数:
//约束函数(检查当前第x行皇后放好后,是否满足约束条件)
int place(int x){
if(x == 1)return 1; //第1行的皇后可以随便放
for(int i = 1; i < x; i ++) //检查当前x行的皇后是否与前x - 1行的皇后同列,同斜线
{
if(p[i] == p[x] || abs(x - i) == abs(p[x] - p[i]))return 0;
}
return 1;
}
/*
1)检查当前位置与之前皇后位置是否处于同一列:p[i] == p[x]
2)检查当前位置与之前皇后位置是否处于同一斜线:abs(x - i) == abs(p[x] - p[i])
因为如果是在同一条斜线上,那么该两个皇后位置的水平差等于竖直差
*/
总概括: 通过一个数组的下标去表示皇后放置的行,数组值去表示皇后放置的列,从而我们可以从1开始递归每一行,然后去遍历当前行的每一列,判断是否可行,如果可行,那么就将该行的皇后放在该列,然后就去放下一行;如果当前行,每一列都不能放,那么就回溯到上一行去放其他的、可行的列位置。直到递归到第n + 1行,也就是说此时,我们前n行的皇后都放完了,即有一个可行方案,输出即可。
复杂度分析:
时间复杂度: O(n ^ (n + 1))
对于n皇后问题的解空间树是一颗n叉树,树的深度是n。最坏情况是所有的点都需要扩展,那么该n叉树所有的点数为1 + n + n^2 + …… + n ^ n = n^n ,每个点都要判断约束条件,即是否可以放,其时间复杂度为O(n),那么所有点都判断的时间复杂度是O(n ^ (n + 1))。对于可行的方案,我们需在叶子结点处输出可行方案,需要时间复杂度O(n),叶子结点数是n^n,因此时间复杂度是O(n ^ (n + 1)),综上,忽略常数,总的时间复杂度是O(n ^ (n + 1))
空间复杂度:O(n)
回溯法的一个重要性质就是在搜索执行的同时产生解空间,在所搜索过程中的任何时刻,仅保留从开始结点到当前扩展结点的路径,从开始结点起最长路径就是数的深度为n,因此,该算法的空间复杂度为O(n)。
代码:
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
int n, flag = 0; //flag 记录当前n皇后问题是否存在可行解
int p[20]; //记录n皇后的解, p[i] 表示第i行的皇后放在第[i]列
//约束函数(检查当前第x行皇后放好后,是否满足约束条件)
//判断第x个皇后能否放在第x行的第p[x]l列
int place(int x){
if(x == 1)return 1; //第1行的皇后可以随便放
for(int i = 1; i < x; i ++) //检查当前x行的皇后是否与前x - 1行的皇后同列,同斜线
{
if(p[i] == p[x] || abs(x - i) == abs(p[x] - p[i]))return 0;
}
return 1;
}
//递归每一行
void dfs(int u){
//递归到第n + 1 行,说明是一个可行解
if(u == n + 1){
flag = 1;
for(int i = 1; i <= n; i ++)printf("%5d", p[i]);
printf("\n");
return;
}
//枚举第u行的每一列
for(int i = 1; i <= n; i ++){
p[u] = i; //将皇后放在第u行第i列
if(place(u)) //如果该皇后可以放在该位置,就去递归下一行
{
dfs(u + 1);
}
}
}
int main(){
printf("请输入n皇后的个数n:");
scanf("%d", &n);
dfs(1);
if(!flag)printf("no solute!\n");
return 0;
}