回溯算法——算法原理及八皇后问题图解分析(超详细)

1回溯算法原理

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

2例题讲解——八皇后问题

2.1题目描述

设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线

输入输出示例
输入:4
输出:[[".Q…","…Q",“Q…”,"…Q."],["…Q.",“Q…”,"…Q",".Q…"]]
解释: 4 皇后问题存在如下两个不同的解法。
[
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],

["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]

2.2直观思路

利用回溯+递归的解法
变量说明:

  • ListString>> ans = new ArrayList<>();存放所有可能的二维列表
  • char[][] res = new char[n][n];放置皇后的二维数组
  • int[][] used = new int[n][n];根据题目条件推出不能放置皇后的的位置,跟 r e s res res数组位置对应

解题思路

  • 1先将数组 r e s res res用.来填充, u s e d used used数组全部初始化为0
  • 2从第一行开始,把Q依次放在0 ~ n的位置
  • 3每次放置Q后,在 u s e d used used数组中标记不能放置皇后的位置(不同列,也不在对角线上,因为接着会到下一行,所以不需要标记当前行)
  • 4下一行放置Q时,根据 r e s res res数组中的空位置放置皇后,若无空位置,返回至上一行;若有空位置则继续探索下一层
  • 5重复以上步骤2~4直到找到最后一行
  • 6当一次搜索完成后执行上面所有步骤继续搜索其它放置路线

2.3图解流程

以下流程讲解当皇后数为n = 4的图解流程

  • 0 \quad r e s 和 u s e d 数 组 初 始 化 如 下 res和used数组初始化如下 resused
    回溯算法——算法原理及八皇后问题图解分析(超详细)_第1张图片
    注意:题目中要求结果中没有皇后的位置为.为了方便观看,这里改为0.
  • 1 \quad 将第一个皇后放置在 r e s [ 0 ] [ 0 ] res[0][0] res[0][0]位置,计算出 u s e d used used数组中剩下行不能放置皇后的位置(1代表不能放置,每次计算出不能放置的位置处后加1)

回溯算法——算法原理及八皇后问题图解分析(超详细)_第2张图片

  • 1.1 \quad 由上图可以看出第二行放置皇后的位置只能是 ( 1 , 2 ) 和 ( 1 , 3 ) (1, 2)和(1, 3) (1,2)(1,3)的位置,尝试将第二个皇后放置在 r e s [ 1 ] [ 2 ] res[1][2] res[1][2]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
    回溯算法——算法原理及八皇后问题图解分析(超详细)_第3张图片

  • 1.2 \quad 可以看出第三行没有能放置皇后空位置,所以第二次选放置皇后的位置行不通,返回至1步骤,尝试将第二个皇后放置在 r e s [ 1 ] [ 3 ] res[1][3] res[1][3]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
    回溯算法——算法原理及八皇后问题图解分析(超详细)_第4张图片

  • 1.3 \quad 可以看出第三行的 ( 2 , 1 ) (2, 1) (2,1)处能够放置皇后,将第三个皇后放置在 r e s [ 2 ] [ 1 ] res[2][1] res[2][1]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
    回溯算法——算法原理及八皇后问题图解分析(超详细)_第5张图片

  • 可以看出第四行没有能够放置皇后的位置,所以由第1步放置在位置 r e s [ 0 ] [ 0 ] res[0][0] res[0][0]是不能完成实现四个皇后的放置。此时回到最初的起点0

  • 2 \quad 将第一个皇后放置在 r e s [ 0 ] [ 1 ] res[0][1] res[0][1]的位置,计算出 u s e d used used数组中剩下行不能放置皇后的位置
    回溯算法——算法原理及八皇后问题图解分析(超详细)_第6张图片

  • 2.1 \quad 由上图可以看出第二行放置皇后的位置只能 ( 1 , 3 ) (1, 3) (1,3)的位置,将第二个皇后放置在 r e s [ 1 ] [ 3 ] res[1][3] res[1][3]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
    回溯算法——算法原理及八皇后问题图解分析(超详细)_第7张图片

  • 2.2 \quad 由上图可以看出第三行放置皇后的位置只能 ( 2 , 0 ) (2, 0) (2,0)的位置,将第三个皇后放置在 r e s [ 2 ] [ 0 ] res[2][0] res[2][0]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
    回溯算法——算法原理及八皇后问题图解分析(超详细)_第8张图片

  • 2.3 \quad 由上图可以看出第四行有且仅有一个位置放置皇后,故将第四个皇后放置在 r e s [ 3 ] [ 2 ] res[3][2] res[3][2]处,就能完成四个皇后的放置,满足题意。
    到此为止,只寻找到一种放置的方法,只需要按此流程,继续寻找其它路线,即可找出所有满足题意的四个皇后的放置

2.4代码实现

class Solution0812 {
    int total  = 0;
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> ans = new ArrayList<>(); // 存放结果的二维链表
        int[][] used = new int[n][n]; // 根据题目条件推出不能放置皇后的的位置,跟res数组位置对应
        char[][] res = new char[n][n]; // 放置皇后的二维数组
        for (int i = 0; i < n; i++) { // 填充所有
            Arrays.fill(res[i], '.');
        }
        solveNQueens(n, used, ans, 0, res); // 回溯法+递归
        System.out.println(Arrays.deepToString(ans.toArray())); // 输出结果
        System.out.println("total = " + total);
        return ans;
    }
    
    // 回溯法+递归
    public void solveNQueens(int n, int[][] used, List<List<String>> ans, int row, char[][] res) {
        for (int column = 0; column < n; column++) { // 从第一行各个列开始向下尝试搜索
            if (used[row][column] == 0) { // 位置为零则表示此处可以放置皇后
                res[row][column] = 'Q';
                if (row == n -1) { // 是否已经搜索到了最后一行,若是则表示一次搜索完成,将结果放置到ans中
                    List<String> temp = new ArrayList<>();
                    for (char[] re : res) {
                        temp.add(new String(re));
                    }
                    ans.add(temp);
                    res[row][column] = '.'; // 重置此位置 继续遍历当前行的下一位置
                    total++;
                    continue;
                }
                addValue(n, used, column, row, 1); // 计算不能插入皇后的位置
                solveNQueens(n, used, ans, row + 1, res); // 下一行是否有空位置插入皇后
                addValue(n, used, column, row, -1); // 若无空位置插入皇后,则返回上一步,回退
                res[row][column] = '.';
            }
        }
    }
    // 当加入皇后,判断以下行不能插入皇后的位置
    public void addValue(int n, int[][] used, int column, int row, int val) {
        used[row][column] += val; // 当前位置已经插入了皇后
        for (int j = 1; row + j < n; j++) {
            used[row + j][column] += val; // 已经插入皇后的所在列不能再插入皇后,置1
            if (column + j < n) used[row + j][column + j] += val; // 右下对角线不能插入皇后,置1
            if (column - j >= 0) used[row + j][column - j] += val; // 左对角线不能插入皇后,置1
        }
    }
}

你可能感兴趣的:(数据结构与算法,算法,java,leetcode,数据结构)