八皇后问题

八皇后问题还是挺有趣的,是回溯法应用的经典问题。

之前看老师演示的时候感觉还是挺简单的,思路挺清晰,代码量也不多,但是隔了几天自己上手去敲代码的时候,发现,不熟练的话还是会遇到一些问题的。(一个是对 for循环判断条件的理解,一个是对回溯返回位置的理解)

八皇后规则:在一块8x8的棋盘上,每一行找一个皇后的位置,一共找到八个皇后的位置,它们的位置不能在同一行,也不能在同                       一斜线上。

对这个问题,从上述可以知道:

1、结果,只需要输出八个皇后的位置即可,那开一个长度为八的一维数组,储存结果即可(数组下标为行,数组值为对应行的列)

2、对于规则,那也好办,直接 for循环遍历数组第n行之前储存的 0到n-1的值,看是否违反规则即可,用于判断的代码如下:

  /*
     * @description: 判断皇后位置是否违规
     * <br/>
     * @param   n 皇后在棋盘上的位置
     * 注意:不要把这里 for循环里的 “i” 搞混了,这里的“i”与 check中的 “n”其实是一样的,都代表第x行
     *      array[i]的值,表示棋盘里第i行的多少列,array[n]也一样
     * @return ture 当前位置符合规则
     *         false 当前位置不符合规则
     * @author 淡
     * @date 2020/3/16 22:49
     */
    public boolean judge(int n) {
        //1、保证现在要摆放的皇后的位置 n,不和之前皇后的位置在同一行,因为列已经定了(单项递增的),是当前 array[]数组的下标
        //2、保证现在要摆放的皇后的位置 n,不和之前皇后在同一条斜线上
        //3、i < n 这个条件,保证了第一次循环时,不会因为 array[i=0] = 0  array[n=0] = 0,而使得整个程序进不去回溯
        for (int i = 0; i < n; i++) {
            if (array[i] == array[n] || Math.abs(i - n) == Math.abs(array[i] - array[n]))
                return false;
        }
        return true;
    }

对于上面的代码,我觉得最应该注意的是 for循环里,i<n  这一条件,切记不要把它写成 i<array.length,因为如果这样的话,首先就会导致在第一次循环的时候,会因为"array[0] = 0, array[0] = 0"这一情况而判断 false,使得整个代码直接就结束回溯退出程序,当时就在这里出了个bug。

对于其他的代码,主要还是对回溯这一概念的理解,理解回溯到的位置,说实话,这个回溯的效率确实不高,它

  1.  是先找到第一个解,然后就由回溯代码里的条件判断(n==max),然后输出解之后,就返回到上一级
  2.  然后把该级由于触发条件进入下一级,而没有执行完的 for循环执行完(目的是期待找出与上一个解只有一位不同的解)

这其实就是暴力破解,把所有的可能性都遍历一遍,下面是回溯的代码:

  /**
     * @param n 可能值为:0-7,用于传给判断函数 judge(),
     *          若返回的是true,则说明当前第 n行的位置 i与之前的棋子不冲突,然后进入 n+1 行的判断
     *          若返回的是false,则说明位置 i 冲突,然后位置 i+1 后继续判断
     * @return void
     * @description: 下棋,使用回溯法确定每一行棋子的位置
     * <br/>
     * @author 淡
     * @date 2020/3/17 22:04
     */
    public void check(int n) {
        //n==max说明已经将八个皇后的位置都确定下来了,打印它们的位置信息,并跳出
        if (n==max){
            print();
            count++;
            return;
        }
        // n 的值是单向的,从0一直递增
        //其实这里,它执行完一次完整的找到八个位置的方法后,它会:
        //1、从 check[n=7] 进入到 check[n=8],但是 check()方法里直接在最开始就拦截了,直接就:输出结果 + 退出check[n=8]
        //2、从 check[n=8] -> check[n=7]之后,它会继续 for循环,
        //     以第一种解法(0 4 7 5 2 6 1 3 )为例,它会继续找 array[7] = 3之后的值,
        //     比如:array[7] = 4,发现都不合适之后,会自动结束 check[n=7],然后又进入check[n=6]里的,因为进入check[n=7]而中断的 for循环
        for (int i = 0; i < array.length; i++) {
            array[n] = i;
            //判断第 n个皇后的位置 i 是否冲突,如果冲突,则 i+1,然后继续判断
            if (judge(n)) {
                check(n + 1);
            }
        }

    }

下面是完整代码:

package com.Chen;

/**
 * @description:
 * @author: 淡
 * @createDate: 2020-03-16 21:42
 * @version: 1.0
 */
public class Queue8 {
    private int max = 8;//最大皇后数
    private int[] array = new int[max];//储存皇后位置的数组
    private static int count = 0;//统计有几种解法
    private static long judgeCount = 0;//统计程序一共回溯了几次
    private static long circulateCount = 0;//统计程序一共回溯了几次


    public static void main(String[] args) {
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("有 %d 种解法\n", count);
        System.out.printf("一共判断了 %d 次\n", judgeCount);
        System.out.printf("一共循环了 %d 次", circulateCount);


    }

    /**
     * @param n 可能值为:0-7,用于传给判断函数 judge(),
     *          若返回的是true,则说明当前第 n列的位置 i与之前的棋子不冲突,然后进入 n+1 列的判断
     *          若返回的是false,则说明位置 i 冲突,然后位置 i+1 后继续判断
     * @return void
     * @description: 下棋,使用回溯法确定每一行棋子的位置
     * <br/>
     * @author 淡
     * @date 2020/3/17 22:04
     */
    public void check(int n) {
        //n==max说明已经将八个皇后的位置都确定下来了,打印它们的位置信息,并跳出
        if (n==max){
            print();
            count++;
            return;
        }
        // n 的值是单向的,从0一直递增
        //其实这里,它执行完一次完整的找到八个位置的方法后,它会:
        //1、从 check[n=7] 进入到 check[n=8],但是 check()方法里直接在最开始就拦截了,直接就:输出结果 + 退出check[n=8]
        //2、从 check[n=8] -> check[n=7]之后,它会继续 for循环,
        //     以第一种解法(0 4 7 5 2 6 1 3 )为例,它会继续找 array[7] = 3之后的值,
        //     比如:array[7] = 4,发现都不合之后,会自动结束 check[n=7],然后又进入check[n=6]里的,因为进入check[n=7]而中断的 for循环
        for (int i = 0; i < array.length; i++) {
            array[n] = i;
            judgeCount++;
            //判断第 n个皇后的位置 i 是否冲突,如果冲突,则 i+1,然后继续判断
            if (judge(n)) {
                check(n + 1);
            }
        }

    }

    /*
     * @description: 判断皇后位置是否违规
     * <br/>
     * @param   n 皇后在棋盘上的位置
     * 注意:不要把这里 for循环里的 “i” 搞混了,这里的“i”与 check中的 “n”其实是一样的,都代表第x行
     *      array[i]的值,表示棋盘里第i行的多少列,array[n]也一样
     * @return ture 当前位置符合规则
     *         false 当前位置不符合规则
     * @author 淡
     * @date 2020/3/16 22:12
     */
    public boolean judge(int n) {
        //1、保证现在要摆放的皇后的位置 n,不和之前皇后的位置在同一行,因为列已经定了(单项递增的),是当前 array[]数组的下标
        //2、保证现在要摆放的皇后的位置 n,不和之前皇后在同一条斜线上
        //3、i < n 这个条件,保证了第一次循环时,不会因为 array[i=0] = 0  array[n=0] = 0,而使得整个程序进不去回溯
        for (int i = 0; i < n; i++) {
            circulateCount++;
            if (array[i] == array[n] || Math.abs(i - n) == Math.abs(array[i] - array[n]))
                return false;
        }
        return true;
    }

    /**
     * @param
     * @return void
     * @description: 输出array数组中储存的皇后的位置,即:本问题的一种解法,然后换行
     * <br/>
     * @author 淡
     * @date 2020/3/17 22:00
     */
    public void print() {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }


}

下面是输出结果的部分截图:

可以看到,使用回溯法,为了找8×8棋盘的八个皇后,一共执行了 46752 次的循环,光是调用判断程序,就调用了 15720 次

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值