八皇后问题

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

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

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

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

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

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

  /*
     * @description: 判断皇后位置是否违规
     * 
* @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,因为如果这样的话,首先就会导致在第一次循环的时候,会因为"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: 下棋,使用回溯法确定每一行棋子的位置
     * 
* @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: 下棋,使用回溯法确定每一行棋子的位置
     * 
* @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: 判断皇后位置是否违规 *
* @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数组中储存的皇后的位置,即:本问题的一种解法,然后换行 *
* @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(); } }

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

八皇后问题_第1张图片

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

你可能感兴趣的:(算法)