问题描述:N 皇后问题源自国际象棋,所有棋子中权力最大的称为皇后,它可以直着走、横着走、斜着走(沿 45 度角),可以攻击移动途中遇到的任何棋子。N 皇后问题的具体内容是:如何将 N 个皇后摆放在 N*N 的棋盘中,使它们无法相互攻击,并且计算有多少存在的可能。
首先,我们通过一个5皇后问题来了解一下皇后问题的大概运算过程!
我们选择从(0,2)的位置开始放入第一个皇后,在有限制的条件下,通过遍历可以很容易的看出,只要最后一层存在可以放的位置,那么就可以摆放n(n也指棋盘的宽度,这里n=5)个皇后。
在看完5皇后问题的部分解后,如何用代码去实现皇后问题的解呢?
第一步 : 用代码去实现限制条件
对角线可以理解为:|x1-x2|=|y1-y2|
static boolean visit(int arr[],int i , int j){
for (int k = 0; k < i; k++) { //k是指行数 j是指列数 因为只有选过后才有限制条件,所以一定存在k小于i(目前已经到达的行数)
if(arr[k]==j||Math.abs(arr[k]-j)==Math.abs(i-k))
return false;
}
return true;
}
第二步 :利用dfs去遍历所有情况并且记录
static int dfs(int arr[],int i){
if(i==n){
return 1;
}
ans=0;
for (int j = 0; j < n; j++) {
if(visit(arr,i,j)){
arr[i]=j;
ans+=dfs(arr,i+1);
}
}
return ans;
}
利用ans去记录成功的存在n皇后的记录
全部代码如下:
static int n;
static int ans;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
int arr[] = new int[n];
System.out.println(dfs(arr, 0));// 0是指从0行开始
}
static int dfs(int arr[],int i){
if(i==n){
return 1;
}
ans=0;
for (int j = 0; j < n; j++) {
if(visit(arr,i,j)){
arr[i]=j;
ans+=dfs(arr,i+1);
}
}
return ans;
}
static boolean visit(int arr[],int i , int j){
for (int k = 0; k < i; k++) { //k是指行数 j是指列数 因为只有选过后才有限制条件,所以一定存在k小于i(目前已经到达的行数)
if(arr[k]==j||Math.abs(arr[k]-j)==Math.abs(i-k))
return false;
}
return true;
}
总结:可以很简单的看出,以上代码解决n-皇后问题的时间复杂度是0(N^2),我可以用二进制稍微优化一点空间复杂度。
2.二进制优化过程。
想象一下,上面代码是将每一行选的数加入数组,而我们只使用一个数的二进制来代表一列,1视为可以选,0视为不可以选,如此我们可以节约一个数组的空间!
限制的条件的产生以及代码的实现
我们需要定义三个变量来控制我们的限制条件分别为 左限制(leftlimit)、列限制(cenlimit)、右限制(rightlimit),限制原理如下图所示:
以选择(0,3)为例,(0,3)一个位置对第二排的限制和对第三排的限制是存在一定的规律的,并且三个变量规律不同一,所以我们选择使用3个变量 左限制(leftlimit)、列限制(cenlimit)、右限制(rightlimit)来控制这种规律。
每过一行,每个数的左限制会向左移动一位,右限制会向右移动一位,列限制不改变。由此得到限制条件的代码 (limit暂时不参与讨论)。
代码实现:
第一、遍历过程中,我们枚举第一行所有的可能性并且在1视为可以选择,0视为不可能选择的情况下,我们需要输入一个数,让他0-n-1位全为1;
int limit = (1<<n)-1;
第二、找到每一行的限制条件,将左右列三个限制合并在一起,因为int类型有32位,所以为了让8-32位上全为0,而且0-7位上可以正常充当限制条件,我们写出如下代码:
int pos = limit&(~(leftlimit|rightlimit|conlimit));
全部代码如下:
static int n ;
static int ans=0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
int limit = (1<<n)-1;
dfs(limit,0,0,0);
System.out.println(ans);
}
static void dfs(int limit,int leftlimit,int rightlimit,int conlimit){
if(conlimit==limit){
ans+=1;
return ;
}
int select ;
int pos = limit&(~(leftlimit|rightlimit|conlimit));
while(pos!=0){
select = pos&(~pos+1);
pos = pos-select;
dfs(limit,(leftlimit|select)<<1,(rightlimit|select)>>>1,conlimit|select);
}
}