回溯算法之八皇后问题深度解析

文章内容全部写在了代码注释中

/**
 * 八皇后算法:回溯算法
 *
 * 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
 * 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。
 * 但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
 * 许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称
 *
 * 代码思路:
 * 采取每行遍历的形式寻找解决方案(成功摆放皇后的方案),找到位置之后表位1,标记列和对角线为1(这些位置都不能存放皇后,默认为0,代表可以放置皇后)
 * 找到一种解决方案后输出结果。
 * 然后回溯到最后一个摆放皇后一个成功摆放皇后的位置,置为0(不放皇后),并标记列和对角线为0(这些位置可以存放),继续去寻找下一个可以摆放皇后的位置。
 * 找到解决方案就输出,若没有解决方案就回溯到倒数第二个摆放皇后的位置,置为0···依次类推
 *
 * 心得:
 * 需要回溯的点就是调用递归方法的地方。
 * 看到回溯算法,因为需要回溯所以想到递归,但是不知何时递归。
 * 通过重新学习八皇后的问题,参考网上的一些代码之后,自己写了一遍程序(鄙人不才,只是独立思考,没有写出来)
 * 更深刻的了解了递归和回溯算法
 *
 * 开发递归方法的技巧:
 * 1.写功能方法时可以事先假设此功能方法已经实现
 * 2.找到回溯的点(每次执行会入栈,执行完跳出栈,继续向下执行的那个点)
 * 3.难点:找到跳出递归方法的条件
 */
public class Queen {

    /** 角标代表列的位置,值代表可否放置皇后,0代表可以放置皇后,1代表不可以放置皇后 */
    private int[] columns;//判断列
    /** 角标代表正向对角线(/)的位置,值代表可否放置皇后,0代表可以放置皇后,1代表不可以放置皇后 */
    /* 二维数组的一维角标和二维角标相加,即i + j
    0 1 2 3 4 5 6 7
    1 2 3 4 5 6 7 8
    2 3 4 5 6 7 8 9
    3 4 5 6 7 8 9 10
    4 5 6 7 8 9 10 11
    5 6 7 8 9 10 11 12
    6 7 8 9 10 11 12 13
    7 8 9 10 11 12 13 14
     */
    private int[] diagonalPositive;//正向对角线
    /** 角标代表反向对角线(\)的位置,值代表可否放置皇后,0代表可以放置皇后,1代表不可以放置皇后 */
    /* 二维数组的一维角标和二维角标相减(谁减谁无所谓,只是数值位置不一样而已),即 i - j (因为数据角标是大于0,所以相减后加一定的值使其大于0,即i - j + (temp - 1))
    7 6 5 4 3 2 1 0
    8 7 6 5 4 3 2 1
    9 8 7 6 5 4 3 2
    10 9 8 7 6 5 4 3
    11 10 9 8 7 6 5 4
    12 11 10 9 8 7 6 5
    13 12 11 10 9 8 7 6
    14 13 12 11 10 9 8 7
     心得:两个对象,变化规律:若是一多一少,和相等;同时多或同时少,差相等*/
    private int[] diagonalNegative;//反向对角线
    /** 二维数组记录解决方案,角标代表棋盘位置(一维角标代表行,二维角标代表列),值代表可否放置皇后,1代表可以放置,0没有放置 */
    private int[][] result;
    /** 存储几皇后,例如八皇后,值记录8 */
    private int n;
    /** 统计所有放置皇后的解决方案 */
    private int count;

    /**
     * 构造方法
     * @param N 几皇后
     */
    public Queen(int N) {
        this.n = N;
        this.columns = new int[N];
        this.diagonalPositive = new int[2 * N - 1];
        this.diagonalNegative = new int[2 * N - 1];
        this.result = new int[N][N];
    }

    /**
     * 唯一对外开发的方法,输出解决方案
     */
    public void showAnswer() {
        queen(0);//从第一行开始寻找摆放位置
    }

    /**
     * 寻找摆放皇后的位置
     * @param row 行角标,从0开始
     */
    private void queen(int row) {
        if (row < this.n) {//行数是否超过棋盘角标限制
            for (int column = 0; column < this.n; column++) {//遍历每一行的每一列的位置,判断是否可以放置皇后
                //计算角标,列和对角线的值都为0,证明这个位置所在的列和对角线上都没有放置皇后
                if (columns[column] == 0 && diagonalPositive[row + column] == 0 && diagonalNegative[row - column + this.n - 1] == 0) {
                    result[row][column] = 1;//解决方案中存储此处放置皇后
                    columns[column] = diagonalPositive[row + column] = diagonalNegative[row - column + this.n - 1] = 1;//标记此处对应的列和对角线都能放置皇后
                    queen(row + 1);//寻找下一行放置皇后的位置,同时也是方法执行完成后的回溯点

                    result[row][column] = 0;//方案输出之后,会回溯到上面的点,重新标记为0,也就是此处不放置皇后,去寻找“这行”下一个摆放皇后的位置(对应这个for循环)
                    columns[column] = diagonalPositive[row + column] = diagonalNegative[row - column + this.n - 1] = 0;//因为没有放置皇后,所以对应的列和对角线重置为0
                }
            }//如果遍历完成都没有找“这行”下个放置皇后的位置,证明没有找到解决方案
        } else {//行数超过棋盘角标限制,证明已经找完整个棋盘。注意:每一种解决方案,每一行必定有且仅有一个皇后。输出解决方案
            System.out.println("第" + ++count + "种解决方案");
            show(result);
        }
    }

    /**
     * 遍历输出数组的值
     * @param arr 目标数组
     */
    private void show(int[][] arr) {
        for (int[] anArr : arr) {
            for (int anAnArr : anArr) {
                System.out.print(anAnArr + "\t");
            }
            System.out.println("\n");
        }
    }

    public static void main(String[] args) {
        Queen q = new Queen(4);
        q.showAnswer();
    }
}


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