数据结构和算法之八皇后问题

无人久伴 提交于 2019-12-28 03:54:40

一、问题描述

将八位皇后放在一张8*8的棋盘上,使得每位皇后都无法吃掉别的皇后。(即任意两个皇后都不在同一条横线,竖线,斜线上),求一共能有多少种摆放的方法。

二、图解问题描述

假设我们放入第一个皇后,位置再第五行第四列上,那么他的同一条横线,竖线,斜线上都不能放其他皇后,如下图
在这里插入图片描述
我们在空白处放入第二个皇后,比如位置:第四行第六列,如下图
在这里插入图片描述

如此下去,能放下一个皇后的位置会越来越少,能同时放下8个皇后的话,我们该如何设计每个皇后的位置呢?可以采取递归回溯的思想。

三、什么是递归回溯

1、概念

我个人总结为:不撞南墙不回头,撞了南墙才回头。

2、图解

为了方便,我就弄4x4的格子了,不搞8x8那么大了。

假设第一个皇后位置再第一行第一列,如下图

在这里插入图片描述
我们放入第二个皇后,假设位置为第二行第三列,如下图

在这里插入图片描述
这样一来前面两位皇后已经把第三行全部锁死了,唯一的空隙就是第四行第二列了,但是第三位皇后如果放到那里的话,第四位皇后就没地方放了,game over!所以我们只能返回上一步,来给第二位皇后换个位置(这种递归到最后发现走不通然后回头移动之前走过的棋子的套路就是回溯,也就是不撞南墙不回头,撞了南墙才回头)。

我们将第二个皇后后移一列(从原来的第二行第三列换成第二行第四列)再看效果,如下图

在这里插入图片描述

此时,第三个皇后有两个位置可以选择,目测效果不错,但实际不管你放到那两个位置的哪个位置上,对于第四个皇后都是死路一条,因为这两个位置是斜线的,这时候我们还需要返回第二个皇后,让第二个皇后继续向右移动,结果发现撞了南墙了(到达最大列长度了,无法继续移动),那只能在第二位皇后的上一级,也就是第一位皇后这里来移动位置,具体如下图

在这里插入图片描述
大概递归回溯就是这么个意思,八皇后也是这么个意思。over!

四、核心代码

1、思路

理论上看到墙就会想到二维数组,但是这个八皇后搞个一维数组就能解决,比如:

arr[8] = {0, 4, 7, 5, 2, 6, 1, 3}

arr的下标表示第几行,即第几个皇后。arr[i] = val, val表示第i+1个皇后,放在第i+1行的第val+1列。比如上面的arr[8]中的0就代表第一行第一列放入第一个皇后,4代表第二行第五列放入第二个皇后。

2、打印代码

private void print() {
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
    }
    System.out.println();
}

3、检查冲突代码

/**
 * 查看当我们放置第N个皇后的时候,就去检测该皇后是否和前面已经摆放的皇后冲突
 *
 * @param n:表示第N个皇后
 * @return
 */
private boolean judge(int n) {
    for (int i = 0; i < n; i++) {
        /*
         * 代表第n个皇后是否和前面的n-1个皇后同列 || 斜线
         *
         * 为什么没判断同一行,因为n代表行,每一次循环都在递增,肯定不在同一行
         */
        if (arr[i] == arr[n] || Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
            return false;
        }
    }
    // 代表不冲突
    return true;
}

查看当我们放置第N个皇后的时候,就去检测该皇后是否和前面已经摆放的皇后冲突。

Math.abs(n - i) == Math.abs(arr[n] - arr[i]):这个算法是判断斜线的。可以代数法进行测试。

4、核心方法

/**
 * 编写一个方法,放置第N个皇后
 * 特别注意,check是每一次递归时,进入到check中都有for(int i = 0; i < max; i++),因此会产生回溯。
 */
private void check(int n) {
    // 说明放到最后了(第8个皇后放完了)
    if (n == max) {
        // 输出解法
        print();
        return;
    }
    // 依次放入皇后,并判断是否冲突
    for (int i = 0; i < max; i++) {
        // 先把当前皇后(n)放到该行的第一列
        arr[n] = i;
        // 判断当放置第N个皇后到i列时,是否冲突
        if (judge(n)) { // true:不冲突
            // 接着放n+1个皇后,即开始递归
            check(n + 1);
        }
        // 如果冲突,就继续执行arr[n] = i,也就是执行i++,即将第n个皇后放置再本行后移的一个位置
    }
}

多想想不撞南墙不回头,撞了南墙才回头这句话,多研究研究,其实很简单。

n=0,进入for,arr[0] = 0; judge(n)=true,递归check(n+1)。若judge(n)=false,则继续执行i++,也就是把这个皇后向右侧移动,也就是+1列,若八个合适的位置都找到了,则直接输出解法+return,若没找到则上一个皇后继续i++,向右侧移动1列,也就是回溯思想。

五、完整代码

package com.chentongwei.struct.recursion;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-27 10:13:11
 */
public class Queue8 {

    // 定义一个max表示共有多少皇后
    int max = 8;
    // 定义数组保存皇后放置的位置
    // 比如{0, 4, 7, 5, 2, 6, 1, 3}
    int[] arr = new int[max];

    static int count;
    static int judgeCount;
    public static void main(String[] args) {
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("一共有%d解法\n", count);
        System.out.printf("一共有%d次冲突数", judgeCount);
    }

    // 编写一个方法,放置第N个皇后
    // 特别注意,check是每一次递归时,进入到check中都有for(int i = 0; i < max; i++),因此会产生回溯。
    private void check(int n) {
        // 说明放到最后了(第8个皇后放完了)
        if (n == max) {
            print();
            return;
        }
        // 依次放入皇后,并判断是否冲突
        for (int i = 0; i < max; i++) {
            // 先把当前皇后(n)放到该行的第一列
            arr[n] = i;
            // 判断当放置第N个皇后到i列时,是否冲突
            if (judge(n)) { // 不冲突
                // 接着放n+1个皇后,即开始递归
                check(n + 1);
            }
            // 如果冲突,就继续执行arr[n] = i,也就是执行i++,即将第n个皇后放置再本行后移的一个位置
        }
    }

    /**
     * 查看当我们放置第N个皇后的时候,就去检测该皇后是否和前面已经摆放的皇后冲突
     *
     * @param n:表示第N个皇后
     * @return
     */
    private boolean judge(int n) {
        judgeCount ++;
        for (int i = 0; i < n; i++) {
            /*
             * 代表第n个皇后是否和前面的n-1个皇后同列 || 斜线
             *
             * 为什么没判断同一行,因为n代表行,每一次循环都在递增,肯定不在同一行
             */
            if (arr[i] == arr[n] || Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
                return false;
            }
        }
        // 代表不冲突
        return true;
    }

    // 写一个方法,可以将皇后摆放的位置输出
    private void print() {
        // 解法次数+1
        count ++;
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

0 4 7 5 2 6 1 3
0 5 7 2 6 3 1 4
0 6 3 5 7 1 4 2
0 6 4 7 1 3 5 2
1 3 5 7 2 0 6 4
1 4 6 0 2 7 5 3
1 4 6 3 0 7 5 2
1 5 0 6 3 7 2 4
1 5 7 2 0 3 6 4
1 6 2 5 7 4 0 3
1 6 4 7 0 3 5 2
1 7 5 0 2 4 6 3
2 0 6 4 7 1 3 5
2 4 1 7 0 6 3 5
2 4 1 7 5 3 6 0
2 4 6 0 3 1 7 5
2 4 7 3 0 6 1 5
2 5 1 4 7 0 6 3
2 5 1 6 0 3 7 4
2 5 1 6 4 0 7 3
2 5 3 0 7 4 6 1
2 5 3 1 7 4 6 0
2 5 7 0 3 6 4 1
2 5 7 0 4 6 1 3
2 5 7 1 3 0 6 4
2 6 1 7 4 0 3 5
2 6 1 7 5 3 0 4
2 7 3 6 0 5 1 4
3 0 4 7 1 6 2 5
3 0 4 7 5 2 6 1
3 1 4 7 5 0 2 6
3 1 6 2 5 7 0 4
3 1 6 2 5 7 4 0
3 1 6 4 0 7 5 2
3 1 7 4 6 0 2 5
3 1 7 5 0 2 4 6
3 5 0 4 1 7 2 6
3 5 7 1 6 0 2 4
3 5 7 2 0 6 4 1
3 6 0 7 4 1 5 2
3 6 2 7 1 4 0 5
3 6 4 1 5 0 2 7
3 6 4 2 0 5 7 1
3 7 0 2 5 1 6 4
3 7 0 4 6 1 5 2
3 7 4 2 0 6 1 5
4 0 3 5 7 1 6 2
4 0 7 3 1 6 2 5
4 0 7 5 2 6 1 3
4 1 3 5 7 2 0 6
4 1 3 6 2 7 5 0
4 1 5 0 6 3 7 2
4 1 7 0 3 6 2 5
4 2 0 5 7 1 3 6
4 2 0 6 1 7 5 3
4 2 7 3 6 0 5 1
4 6 0 2 7 5 3 1
4 6 0 3 1 7 5 2
4 6 1 3 7 0 2 5
4 6 1 5 2 0 3 7
4 6 1 5 2 0 7 3
4 6 3 0 2 7 5 1
4 7 3 0 2 5 1 6
4 7 3 0 6 1 5 2
5 0 4 1 7 2 6 3
5 1 6 0 2 4 7 3
5 1 6 0 3 7 4 2
5 2 0 6 4 7 1 3
5 2 0 7 3 1 6 4
5 2 0 7 4 1 3 6
5 2 4 6 0 3 1 7
5 2 4 7 0 3 1 6
5 2 6 1 3 7 0 4
5 2 6 1 7 4 0 3
5 2 6 3 0 7 1 4
5 3 0 4 7 1 6 2
5 3 1 7 4 6 0 2
5 3 6 0 2 4 1 7
5 3 6 0 7 1 4 2
5 7 1 3 0 6 4 2
6 0 2 7 5 3 1 4
6 1 3 0 7 4 2 5
6 1 5 2 0 3 7 4
6 2 0 5 7 4 1 3
6 2 7 1 4 0 5 3
6 3 1 4 7 0 2 5
6 3 1 7 5 0 2 4
6 4 2 0 5 7 1 3
7 1 3 0 6 4 2 5
7 1 4 2 0 6 3 5
7 2 0 5 1 4 6 3
7 3 0 2 5 1 6 4
一共有92解法
一共有15720次冲突数

六、吹逼

可以去4399找在线八皇后小游戏,然后把上面的输出结果(92种),每一种都放上去跑。你会发现你是个大牛,这不就是外挂吗?直接Java求解过全关小游戏。

比如最后一种解法:7 3 0 2 5 1 6 4

1.就是第一行第八列放入第一个皇后

2.第二行第四列放入第二个皇后

3.第三行第一列放入第三个皇后

4.第四行第三列放入第四个皇后

5.第五行第六列放入第五个皇后

6.第六行第二列放入第六个皇后

7.第七行第七列放入第七个皇后

8.第八行第五列放入第八个皇后

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!