N皇后问题 (史上最快解法 位运算)

欢迎访问https://blog.csdn.net/lxt_Lucia~~

宇宙第一小仙女\(^o^)/~萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗~

 

--------------------------------我只是一条可爱哒分界线-------------------------------

 

今天在论坛上有幸看到了一个难得一见的算法,运用了位运算,应该是目前为止N皇后问题的最快解法了。

 

一、问题:

Description

在 n×n 格的棋盘上放置彼此不受攻击的n 个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何 2 个皇后不放在同一行或同一列或同一斜线上。 设计一个解n 后问题的队列式分支限界法,计算在n× n个方格上放置彼此不受攻击的n个皇后的一个放置方案。

 

Input

共有若干行,每行一个正整数 N≤20,表示棋盘和皇后的数量;如果N=0,表示结束。

 

Output

共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量。

 

Sample Input

1

8

5

0

 

Sample Output

1

92

10

 

二、题意:

中文题,不解释。注意斜着也不可以在同一直线上,包括 左斜 (斜率为1) 和 右斜 (斜率为-1)。

 

三、思路:

核心的是在针对试探-回溯算法所用的数据结构的设计上。
程序采用了递归,也就是借用了编译系统提供的自动回溯功能。

算法的核心:使用bit数组来代替以前由int或者bool数组来存储当前格子被占用或者说可用信息,从这可以看出N个皇后对应需要N位表示。


巧妙之处在于:以前我们需要在一个N*N正方形的网格中挪动皇后来进行试探回溯,每走一步都要观察和记录一个格子前后左右对角线上格子的信息;采用bit位进行信息存储的话,就可以只在一行格子也就是(1行×N列)个格子中进行试探回溯即可,对角线上的限制被化归为列上的限制。


程序中主要需要下面三个bit数组,每位对应网格的一列,在C中就是取一个整形数的某部分连续位即可。
row用来记录当前哪些列上的位置不可用,也就是哪些列被皇后占用,对应为1。
ld,rd同样也是记录当前哪些列位置不可用,但是不表示被皇后占用,而是表示会被已有皇后在对角线上吃掉的位置。这三个位数组进行“或”操作后就是表示当前还有哪些位置可以放置新的皇后,对应0的位置可放新的皇后。如下图所示的8皇后问题求解得
row:      [ ] [ ] [ ] [ ] [ ] [ ] [ ] [*]
ld:         [ ] [ ] [ ] [ ] [ ] [ ] [*] [ ]
rd:         [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]
------------------------------------
row | ld | rd:  [ ] [ ] [ ] [ ] [ ] [ ] [*] [*]


所有下一个位置的试探过程都是通过位操作来实现的,这是借用了C语言的好处,详见代码注释。

 

四、代码:

#include <cstdio>
#include <cstring>
 
using namespace std;
typedef long long ll;
 
ll a[11], sum, mark;
 
void test(ll row, ll ld, ll rd)
//row表示已经有皇后的行
//ld、rd分别表示已经有皇后的左右斜列,每一位表示该列中可以摆放皇后的位置,1表示可以摆放,0表示不可以摆放。row在每查找下一列时不用改动,ld、rd需要分别左移和右移一位。
{
    if(row != mark) //当所有行都有皇后时说明摆放完毕
    {
        ll pos = mark & ~(row | ld | rd); //pos的每一位表示该列中可以摆放皇后的位置,1表示可以摆放,0表示不可以摆放
        while(pos) //如果pos中还有可以摆放的位置
        {
            ll p = pos & -pos; //p为pos中最末一个可以摆放的位置
            pos -= p; //将p从可摆放位置去掉
            test(row + p, (ld + p) << 1, (rd + p) >> 1); //对下一列进行判断
        }
    }
    else
        sum++;
}
 
int main()
{
    int n;
    memset(a, 0, sizeof(a));
    while(~scanf("%d", &n) && n)
    {
        sum = 0, mark = 1;
        if(a[n] == 0)
        {
            mark = (mark << n) - 1; //有多少个皇后,就有多少bit被置1。(从低位到高位)
            test(0, 0, 0);
            a[n] = sum;
        }
        printf("%lld\n", a[n]);
    }
}

 

参考数据:

n   a [n]
1 1
2 0
3 0
4 2
5 10
6 4
7 40
8 92
9 352
10 724
11 2680
12 14200
13 73712
14 365596
15 2279184
16 14772512
17 95815104
18 666090624
19 4968057848
20 39029188884
21 314666222712
22 2691008701644
23 24233937684440
24 227514171973736
25 2207893435808352

 

参考论坛:https://bbs.csdn.net/topics/80489768

--------------------------------我也是有底线的---------------------------------

宇宙第一小仙女\(^o^)/~萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗~

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
n皇后问题是一个经典的回溯算问题,其时间复杂度不可避免的是指数级别的,因此不存在快速解法。 下面给出一个常规的n皇后问题的回溯算实现示例,该实现使用了一个大小为n的一维数组来记录每行所放置的皇后所在列的位置,具体实现如下: ``` #include <iostream> #include <vector> using namespace std; void n_queens(int n, vector<int>& queens, vector<vector<int>>& result) { if (queens.size() == n) { result.push_back(queens); return; } for (int i = 0; i < n; i++) { bool flag = true; for (int j = 0; j < queens.size(); j++) { if (queens[j] == i || abs(queens[j] - i) == abs(j - queens.size())) { flag = false; break; } } if (flag) { queens.push_back(i); n_queens(n, queens, result); queens.pop_back(); } } } int main() { int n = 8; // 设置棋盘大小n vector<int> queens; vector<vector<int>> result; n_queens(n, queens, result); cout << "Total solutions: " << result.size() << endl; for (int i = 0; i < result.size(); i++) { for (int j = 0; j < result[i].size(); j++) { cout << result[i][j] << " "; } cout << endl; } return 0; } ``` 该实现中的`queens`数组记录了每行所放置的皇后所在列的位置,`result`数组用于存储所有的解。在`n_queens`函数中,首先判断当前行是否已经放置完毕,如果已经放置完毕,则将当前解存储到`result`数组中,然后返回上一层。如果当前行还没有放置完毕,那么就在当前行的所有列中依次尝试放置皇后,如果放置皇后后不会与之前的皇后冲突,则继续递归到下一行。如果当前行所有列都无放置皇后,则返回上一层。 该实现的时间复杂度为O(n!),空间复杂度为O(n)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值