回溯算法-Python

回溯算法

回溯算法是一种系统的搜索算法,用于解决诸如排列组合、子集生成、图的路径、棋盘问题等问题。其核心思想是通过递归尝试各种可能的解决方案,遇到不满足条件的解时则回退(回溯),继续尝试其他可能性,直到找到所有的解决方案或确认无解。

主要步骤:

  1. 选择路径: 在当前步骤选择一个可能的路径。
  2. 递归探索: 对当前选择的路径进行递归探索,继续尝试后续步骤。
  3. 约束检查: 检查当前选择是否满足问题的约束条件。如果不满足,则回溯。
  4. 回溯; 如果当前路径无法导致有效解,则回退到上一步,尝试其他可能的路径。
# 回溯算法框架
result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

1.没有重复数字的全排列

leetcode类似问题: 46. 全排列

给出一组数字,返回该组数字的所有排列
例如:[1,2,3]的所有排列如下
[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1]

class Solution:
    def __init__(self):
        self.res = []

    def backtrack(self, nums, path, used):
        # 满足结束条件
        if len(path) == len(nums):
            self.res.append(path.copy())
            return
        for i in range(len(nums)):
            # 剔除已选择的元素
            if used[i]:
                continue
            # 做选择
            path.append(nums[i])
            used[i] = True
            self.backtrack(nums, path, used)
            # 撤销选择
            path.pop()
            used[i] = False

    def permute(self, nums):
        # 记录路径
        path = []
        # 标记选择是否被使用
        used = [False] * len(nums)
        self.backtrack(nums, path, used)
        return self.res

另一种写法

class Solution:
    def permute(self, nums):
        result = []

        def backtrack(start):
            # 当 start 等于 nums 的长度时,表示当前排列完成
            if start == len(nums):
                # 将当前排列加入结果列表
                result.append(nums[:])
                return
            for i in range(start, len(nums)):
                # 交换当前元素与 start 位置的元素
                nums[start], nums[i] = nums[i], nums[start]
                # 递归生成排列
                backtrack(start + 1)
                # 回溯,恢复数组的状态
                nums[start], nums[i] = nums[i], nums[start]

        backtrack(0)
        return result

2.生成所有子集

leetcode类似问题: 78. 子集

给定一个集合 [1, 2, 3],我们需要生成这个集合的所有可能的子集。

class Solution:
    def subsets(self, nums):
        res = []
        path = []

        def backtrack(nums, start, res, path):
            # 将当前路径加入结果中
            res.append(path.copy())
            # 从start位置开始,尝试添加更多的元素到路径中
            for i in range(start, len(nums)):
                # 做选择
                path.append(nums[i])
                # 递归调用
                backtrack(nums, i + 1, res, path)
                # 撤销选择(回溯)
                path.pop()

        backtrack(nums, 0, res, path)
        return res

3.数独问题

leetcode类似问题: 37. 解数独

根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。

class Solution:
    def is_valid(self, board, row, col, num):
        for i in range(9):
            # 检查行
            if board[row][i] == num:
                return False
            # 检查列
            if board[i][col] == num:
                return False
        # 检查3x3子网格
        start_row = (row // 3) * 3
        start_col = (col // 3) * 3
        for i in range(start_row, start_row + 3):
            for j in range(start_col, start_col + 3):
                if board[i][j] == num:
                    return False
        return True

    def solveSudoku(self, board):
        for i in range(9):
            for j in range(9):
                if board[i][j] == ".":
                    # 尝试填入数字1-9
                    for n in "123456789":
                        if self.is_valid(board, i, j, n):
                            board[i][j] = n
                            # 递归调用,尝试解决下一个空位置
                            if self.solveSudoku(board):
                                return True
                            # 如果递归调用返回 False,撤销当前填入的 n
                            board[i][j] = "."
                    # 如果所有数字都尝试过还不能解决,返回 False,触发回溯
                    return False
        return True

4.八皇后问题

leetcode类似问题: 51. N 皇后

在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

class Solution:
    def is_valid(self, board, row, col):
        # 检查列是否有皇后
        for i in range(row):
            if board[i][col] == 'Q':
                return False

        # 检查左上对角线是否有皇后
        for i, j in zip(range(row, -1, -1), range(col, -1, -1)):
            if board[i][j] == 'Q':
                return False

        # 检查右上对角线是否有皇后
        for i, j in zip(range(row, -1, -1), range(col, len(board), 1)):
            if board[i][j] == 'Q':
                return False
        return True

    def backtrack(self, board, row, res):
        # 如果所有行都放置了皇后,表示找到一个解决方案
        if row == len(board):
            res.append(board.copy())
            return
        for col in range(len(board)):
            if self.is_valid(board, row, col):
                # 做选择
                board[row] = board[row][:col] + 'Q' + board[row][col + 1:]
                # 进入下一行决策
                self.backtrack(board, row + 1, res)
                # 撤销选择
                board[row] = board[row][:col] + '.' + board[row][col + 1:]

    def solveNQueens(self, n):
        res = []
        # 初始化棋盘
        board = ["." * n for _ in range(n)]
        self.backtrack(board, 0, res)
        return res

参考资料

  1. https://labuladong.online/algo/essential-technique/backtrack-framework-2/
posted @ 2024-08-05 23:11  rustling  阅读(12)  评论(0编辑  收藏  举报