n皇后问题的回溯and位运算解法

题目内容:
n皇后游戏要求在一个n×n的棋盘上放置n个皇后,使n个皇后互相不攻击(攻击的含义是有两个皇后在同一行或同一列或同一对角线上)。求有多少种放置方法。

① 回溯法

未经优化的暴力解法。虽然很暴力,而且一般就会TLE,但是简单易懂,会给一些优化代码做铺垫。

int c[N];
void dfs(int cur,int n){

  if(cur==n) ans++;
  else for(int i=0;i<n;i++){
    int flag=1;c[cur]=i;
    for(int j=0;j<cur;j++)
      if(c[cur]==c[j]||c[cur]-cur==c[j]-j||c[cur]+cur==c[j]+j){
        flag=0;
        break;
      }
    if(flag)
        dfs(cur+1,n);
  }
}

稍微有点优化的解法,对于每一次判断皇后的放置,再次遍历一遍是太复杂了,不妨用一个二维数组 v i s [ 3 ] [ n ] vis[3][n] vis[3][n]记录列、主对角线、副对角线是否合法。

int vis[3][N];
int c[N];
void dfs1(int cur,int n){

  if(cur==n) ans++;
  else for(int 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;
        dfs1(cur+1,n);
        vis[0][i]= vis[1][cur+i]= vis[2][cur-i+n]=0;
    }
  }
}

② 位运算法

上面的回溯法,需要用到遍历以前的点或者开一个数组记录,那我们可不可以用别的方法来记录呢?答案是肯定的,下面介绍一下位运算如何求解n皇后问题。
至于位运算不熟悉的伙伴们自行找某度。

解题思路:
仍然是一行一行的扫描,我们用1来表示这一列(col)、主对角线(rd)、副对角线(ld)上已经放了皇后。用二进制来表示这一行的情况。
在这里插入图片描述

以四皇后为例,(正方形画的不太好qwq)
当前的情况用二进制表示即为
列(col):0101
主对角线(rd):0010
副对角线(ld):1100
这三个二进制进行或“|”运算,则有

  0101
| 0010
| 1100
————————
  1111

因为二进制上全是1,即下一行的位置都不合法。如果有0,那么这个位置就是合法位置。譬如:
在这里插入图片描述
列(col):0101
主对角线(rd):0001
副对角线(ld):0010
或一下

  0101
| 0001
| 0010
————————
  0111

那么就把第三行的皇后放在第一列。
现在我们解决了第一个问题,如何记录已经放过的皇后。
我们经过或运算之后,1的地方不合法,但是通常我们用1来表示合法,不妨在上面的基础上取反(~),但是取反之后高位上的0都成了1。我们可以进行下面的操作,就成功把高位还原了。

col= 0101;
rd = 0001;
ld = 0010;
(~(col|rd|ld))&((1<<n)-1)

现在我们面临着的问题是,如在在下一列中选择格子。
我们已经知道了,下一列可以选择的格子(1的格子)位置为p,然后从右边依次选取进行递归。
下一次递归的参数,
列 p | col
主对角线:(rd|p)>>1
副对角线:(ld|p)<<1。
解释一下:
在这里插入图片描述
根据图,很显然,下一行col=col|p
主对角线的右下方一定不能放皇后,所以在下一行的时候要右移。
同理,副对角线的左下方一定不能放皇后,所以在下一行的时候要左移。

int ans=0;
void dfs(int n,int row,int col,int ld,int rd){
  if(row>=n){
    ans++;
    return ;
  }
  int bit =(~(col|ld|rd))&((1<<n)-1);
  while(bit>0){
    int p = bit&(-bit);//选取最右边的1
    dfs(n,row+1,col|p,(ld|p)<<1,(rd|p)>>1);//
    bit=bit&(bit-1);//将最右边的1改为0
  }
}

参考:
https://blog.csdn.net/kexuanxiu1163/article/details/105304183?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值