从 DFS 到回溯法,再看 N 皇后问题

简介: DFS 是深度搜索,是暴力的,是一条道走到黑的,是一次性搜到底的!那么,搜到底发现没有路了,就得回退去找另外的路,再继续莽着搜!既然要回退,就必须保存走过每个点的所有信息,包括先后顺序;这个回退的过程就叫 回溯。

image.png

上一篇 已经讲到了 DFS 一些基础的点,由于 DFS 太重要了,不得不再往前深挖一步!

DFS 是深度搜索,是暴力的,是一条道走到黑的,是一次性搜到底的!那么,搜到底发现没有路了,就得回退去找另外的路,再继续莽着搜!既然要回退,就必须保存走过每个点的所有信息,包括先后顺序;这个回退的过程就叫 回溯


根据回溯思想,演进到回溯算法来解决寻找问题。看一下wiki对回溯法的解释:


回溯法采用 试错 的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现,现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。


简化理解:回溯算法 = 树的深度优先搜索 + 剪枝函数

  • 什么是剪枝函数? 为了提高搜索效率,在搜索过程中使用约束函数,可以避免无谓地搜索那些已知不含答案状态的子树。如果是最优化问题,还可以使用限界函数剪去那些不可能含有最优解的子树。约束函数和限界函数目的相同,都是为了剪去不必要搜索的子树,减少问题求解所需实际生成的状态结点数,他们统称为剪枝函数。


OK,以上是概念介绍,下面来一道经典之经典之经典的回溯算法题:N皇后


n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

image.png


示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。


示例 2:

输入: n = 1
输出: [["Q"]]


N 皇后问题很多时候作为例题出现在教科书中,可以当做理解回溯算法的例题进行学习;

以 4 皇后问题为例,递归树如下:

image.png

解题思路:

  • 回溯算法的通用解题思路就是在递归之前做选择,在退出递归之前撤销选择;
  • 通过恰当的方式将不符合条件的情况剪枝;


回溯三部曲:

  • 递归函数参数;
  • 递归终止条件;
  • 单层搜索的逻辑;


回溯模板:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}


JavaScript 实现:

var solveNQueens = function(n) {
    function isValid(row, col, chessBoard, n) {
        // 检查列
        for(let i = 0; i < row; i++) { // 这是一个剪枝
            if(chessBoard[i][col] === 'Q') {
                return false
            }
        }
        // 检查 45度角是否有皇后
        for(let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if(chessBoard[i][j] === 'Q') {
                return false
            }
        }
        // 检查 135度角是否有皇后
        for(let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if(chessBoard[i][j] === 'Q') {
                return false
            }
        }
        return true
    }
    // 存放结果
    function transformChessBoard(chessBoard) {
        let chessBoardBack = []
        chessBoard.forEach(row => {
            let rowStr = ''
            row.forEach(value => {
                rowStr += value
            })
            chessBoardBack.push(rowStr)
        })
        return chessBoardBack
    }
    let result = []
    // 回溯
    function backtracing(row,chessBoard) {
        if(row === n) { // 终止条件
            result.push(transformChessBoard(chessBoard))
            return
        }
        for(let col = 0; col < n; col++) {
            if(isValid(row, col, chessBoard, n)) {
                chessBoard[row][col] = 'Q' // 处理节点
                backtracing(row + 1,chessBoard) // 递归
                chessBoard[row][col] = '.' // 回溯,撤销处理结果
            }
        }
    }
    let chessBoard = new Array(n).fill([]).map(() => new Array(n).fill('.'))
    backtracing(0,chessBoard)
    return result
};


代码作者:carlsun-2,已验证通过;


回溯算法跟 DFS 深度搜索算法都很经典,需同步理解,对比、吸收;

我是掘进安东尼,公众号同名,日拱一卒、日掘一金,再会~



相关文章
|
算法
【学会动态规划】最小路径和(9)
【学会动态规划】最小路径和(9)
41 0
|
6月前
|
存储 人工智能
深度优先搜索(DFS、深搜)和广度优先搜索(BFS、广搜)(中)
深度优先搜索(DFS、深搜)和广度优先搜索(BFS、广搜)
42 0
|
6月前
|
算法
深度优先搜索(DFS、深搜)和广度优先搜索(BFS、广搜)(上)
深度优先搜索(DFS、深搜)和广度优先搜索(BFS、广搜)
82 0
|
6月前
深度优先搜索(DFS、深搜)和广度优先搜索(BFS、广搜)(下)
深度优先搜索(DFS、深搜)和广度优先搜索(BFS、广搜)
35 0
|
6月前
八皇后问题与其深度优先搜索 (DFS) 解法
八皇后问题与其深度优先搜索 (DFS) 解法
71 1
|
移动开发 算法
DFS深度优先算法 —— AcWing 842. 排列数字AcWing 843. n-皇后问题
DFS深度优先算法 —— AcWing 842. 排列数字AcWing 843. n-皇后问题
102 0
|
Java
BFS广度优先遍历——Acwing 844. 走迷宫
BFS广度优先遍历——Acwing 844. 走迷宫
80 0
八皇后(dfs全排列)
八皇后(dfs全排列)
79 0
图的遍历——BFS、DFS
图的两种遍历,深度优先遍历DFS、广度优先遍历BFS
334 1
图的遍历——BFS、DFS