如何在10行代码内解决8皇后问题

  这几天刷知乎看到一个帖子如何用 C++ 在 10 行内写出八皇后?,答案内大神云集,用位运算解决的方案令人叹为观止,于是我也复习了一下这个问题,用 java 把主流方案尝试实现了一遍,收获很大。

N 皇后问题

  N 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。这就要求每行,每列,以及每个棋子对角线上只有一个皇后。详情可以参考 leecode-cn n皇后问题

8-queens.png

  例如,上图就是8皇后问题的一种解决方案

问题分析

解决8皇后问题的关键点在于:

1. 数据结构:棋子摆放的数据结构如何表示

  • 二维数组;
    最直观,用二维数组表示棋盘,每个位置用0,1表示是否有棋子;
private Integer[][] chessBoard;

1 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 1 0 0
0 0 1 0 0 0 0 0
0 0 0 0 0 0 1 0
0 1 0 0 0 0 0 0
0 0 0 1 0 0 0 0

这样,判断当前位置是否合法的函数应当为

    public boolean isValidPosition(int inputX, int inputY) {
        //current row, current column is empty
        for (int i = 0; i < DIMENSION; i++) {
            if (chessBoard[i][inputX] == 1) {
                return false;
            }
            if (chessBoard[inputY][i] == 1) {
                return false;
            }
        }

        // check diagonal
        for (int y = 0; y < DIMENSION; y++) {
            for (int x = 0; x < DIMENSION; x++) {
                // get a chess
                if (chessBoard[y][x] == 0) {
                    continue;
                }
                // check if chess is on diagonal line;
                // y = kx+b; k = 1, -1
                if (Math.abs(y - inputY) == Math.abs(x - inputX)) {
                    return false;
                }
            }
        }
        return true;
    }
  • 一维数组;
    仔细观察上面的二维数组,由于每行只有一个棋子,所以每行有大量的剩余空间,因此可以设计一个一维数组表示棋子的摆放位置。一维数组的下标就是行号y,一维数组的值就是x。
    例如:上面的这个解

1 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 1 0 0
0 0 1 0 0 0 0 0
0 0 0 0 0 0 1 0
0 1 0 0 0 0 0 0
0 0 0 1 0 0 0 0

可以用一维数组表示为

0 4 7 5 2 6 1 3

    private int[] position;

因此,判断当前位置是否合法的函数可以表示为:

public boolean isAvailable(int y) {
    for (int i = 0; i < y; i++) {
        // delta y = delta x; not same column;
        if (Math.abs(y - i) == Math.abs(position[y] - position[i])
           || position[y] == position[i]) {
            return false;
        }
    }
    return true;
}
  • 比特位表示;
    实际上是一维数组的优化版本。最终10行代码解决问题的方案,就采用比特位的数据结构。感兴趣的话可以看一下下面这个版本,本文限于篇幅不再展开。
#include <cstdio>
int queen(int l, int r, int m, int k){
    int ans = 0;
    for (int i = (~(l | r | m)) & 0xff; i; i -= i & -i)
        ans += queen((l | (i & -i)) << 1, (r | (i & -i)) >> 1 , m | (i & -i), k + 1);
    return k == 8 ? 1 : ans;
}
int main(){
    printf("%d\n", queen(0, 0, 0, 0));
}

作者:Comzyh
链接:https://www.zhihu.com/question/28543312/answer/41233325
来源:知乎

2. 算法:回溯法的实现方案

  解决n皇后问题有多种方案,例如全排列暴力破解等;其中最常用的方案是回溯法,深度优先遍历解空间:

  • 从上往下逐行遍历棋盘
  • 每行逐一试探放置棋子
  • 如果当前位置可以摆放棋子(横、竖、对角线都没有冲突),摆放下一行
  • 如果当前位置不能放棋,尝试下一个位置
  • 直到当前行都没有合适位置,需要回退到上一行,让上一行的棋子移动到下一个位置,继续试探。
  • 如果当前行号是最大行号,则问题解决,找到一个n皇后问题的解。

  上述算法可以直观的表现为下图的方式:


8-Queens.gif

算法实现

  回溯法有两种常用的实现方案:

递归回溯

  函数递归的方式编码较为容易,利用编程语言的递归方式,自然形成一个调用栈,方便回溯。

Talk is cheap,show me the code

public int getSolutionCount(int currentLayer) {
    if (currentLayer == DIMENSION) {
        solutionCount++;
    } else {
        for (int x = 0; x < DIMENSION; x++) {
            position[currentLayer] = x;
            if (isAvailable(currentLayer)) {
                getSolutionCount(currentLayer + 1);
            }
        }
    }
    return solutionCount;
}

迭代回溯

  由于递归方式依赖编程语言本身的调用栈,在N非常大的场景下存在效率问题,在正常工作中我们都会采用迭代的方式实现相同的逻辑。
  如下代码利用 currentLayer 指针自增和自减实现类似的入栈和出栈的逻辑:

    public int getSolutionCountIter() {
        position[0] = POSITION_NOT_SET;
        int currentLayer = 0;

        while (currentLayer >= 0) {
            position[currentLayer]++;
            // find a valid position in current layer
            while (position[currentLayer] < DIMENSION && !isAvailable(currentLayer)) {
                position[currentLayer]++;
            }
            // successfully find a position in current layer
            if (position[currentLayer] < DIMENSION) {
                if (currentLayer == DIMENSION - 1) {
                    // get a solution.
                    solutionCount++;
                } else {
                    // continue to check next layer.
                    currentLayer++;
                    position[currentLayer] = POSITION_NOT_SET;
                }
            } else {
                // failed to find a position in current layer
                // move to upper layer.
                currentLayer--;
            }
        }

        return solutionCount;
    }

打印结果

  如果不是需要获取N皇后解的数量,而是需要输出所有的解,则上述算法在solutionCount++的地方保存position数组即可,这里不再详述。详细代码可以参考github。

总结

  N皇后问题的关键是回溯算法,算法可以利用递归实现,也可以利用指针和数组迭代实现。基于比特位表示的算法,复杂度和一维数组是同一个数量级,只是系数更小,相对快一些。从学习的角度,一维数组的算法已经足够了。

GitHub

  最后本文完整程序代码的github链接为,欢迎参考:
https://github.com/lixiangthinker/NQueens

参考

用Dagger2+MVVM写个APP,更直观的展示8皇后算法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容