回溯算法的原理和实现步骤

本文深入解析回溯法的基本原理,通过实例演示如何利用回溯法解决复杂问题,包括LeetCode经典题目如组合总和、全排列等,强调了决策树遍历、路径选择与回退的重要性。

1.原理

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

2.操作

也就是说解决一个回溯问题,实际上就是一个决策树的遍历过程。在这个过程中只需要思考三个问题:
(1)路径:也就是已经做出的选择;
(2)选择列表:也就是你当前可以做的选择;
(3)结束条件:也就是1到达决策树底层,无法再做选择的条件
回溯算法框架:

result = []
def backtrack(路径, 选择列表):
	if 满足结束条件:
		result.add(路径)
		return
	
	for 选择 in 选择列表:
		做选择
		backtrack(路径, 选择列表)
		撤销选择

核心是for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。

3.例子

例一:

Leetcode算法题库里面的39题:
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
在这里插入图片描述

代码:

class Solution:
    def combinationSum(self, candidates, target):
        size = len(candidates)
        candidates.sort()
        print(candidates[0])
        res = []
        path = []
        self.backtrack(res, candidates, target, path, size)
        return res


    def backtrack(self, res, candidates, target, path, size):

        if target == 0:
            x = path[:]
            x.sort()
            if x not in res:
                res.append(path[:])
            return
        for index in range(0, size):
            residue = target - candidates[index]
            if residue < 0:  # 剪枝操作,优化代码
                break
            path.append(candidates[index])
            self.backtrack(res, candidates, residue, path, size)
            path.pop()


if __name__ == '__main__':
    candidates = [2, 3, 6, 7]
    target = 7
    solution = Solution()
    result = solution.combinationSum(candidates, target)
    print(result)

结果:

[[2, 2, 3], [7]]

其中路径是path,选择列表是candidates,结束条件是 if target == 0,做选择用了一个append,撤销选择用了一个出栈的操作。因为candidates中的数据可以无限重复选取,所以每次递归的选择列表都不变。

例二:

Leetcode算法题库里面的46题
在这里插入图片描述
代码:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def backtrack(path, nums):
        	# 如果满足满足结束条件则添加路径
            if len(path) == n:
                path_s = path.copy()
                result.append(path_s)
                return
            for i in range(0, n):
                if nums[i] not in path: #每个数字只能用一次
                	# 路径选择
                    path.append(nums[i])
                    # 回溯
                    backtrack(path, nums)
                    # 撤销路径
                    path.pop()

        result = []
        n = len(nums)
        path = []
        backtrack(path, nums)
        return result

例三:

[Leetcode算法题库里面的47题
在这里插入图片描述代码:

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        def backtrack():
        	# 如果满足满足结束条件则添加路径
            if len(path) == n and path not in result:
                path_s = path.copy()
                result.append(path_s)
                return
            for i in range(0, n):
            	# 路径选择
            	# 设置index可以保证不做重复选择
                if i not in index:
                    index.append(i)
                    path.append(nums[i])
                else:
                    continue
                # 回溯
                backtrack()
                # 撤销选择
                path.pop()
                index.pop()

        result = []
        n = len(nums)
        index = []
        path = []
        backtrack()
        return result

参考labuladong的博文
Leetcode上的代码

例四:

def subsets(nums):
    def backtrack(first, path):
        if len(path) == k:
            path_s = path.copy()
            result.append(path_s)
            return

        for i in range(first, n):
            if nums[i] not in path:
                # 是否要对第i+1个item进行回溯,前面的i个item已经判断完了,不用管
                path.append(nums[i])
                backtrack(i+1, path)
                path.pop()

    n = len(nums)
    path = []
    result = []
    # 表示从nums中取k个item
    for k in range(n+1):
        backtrack(0, path)
    return result

nums = [1,2,3]
print(subsets(nums))

leetcode 78:子集

例五

"practice_17.py" 28L, 791C
def letterCombinations(digits):
    def backtrack(index, path):
        if n == 0:
            return []
        if len(path) == n:
            result.append(''.join(path[:]))
            return

        for letter in digit2letter_dict[digits[index]]:
            path.append(letter)
            backtrack(index+1, path)
            path.pop()

    digit2letter_dict = {'2':['a', 'b', 'c'], '3':['d', 'e', 'f'],
                         '4':['g', 'h', 'i'], '5':['j', 'k', 'l'],
                         '6':['m', 'n', 'o'], '7':['p', 'q', 'r', 's'],
                         '8':['t', 'u', 'v'], '9':['w', 'x', 'y', 'z']}
    n = len(digits)
    path = []
    result = []
    backtrack(0, path)
    return result


digits = "23"
# digits = "2"
digits = ""
print(letterCombinations(digits))

leetcode的第17题

例六:二维平面上使用回溯

Leetcode算法题库里面的79题

剑指offer

回溯法是一种选优搜索法,又称试探法,它按选优条件向前搜索以达到目标。当探索到某一步时,若发现原先选择不优或达不到目标,就退回一步重新选择,满足回溯条件的某个状态的点称为“回溯点”[^3]。 回溯法的核心思想是深度优先搜索(DFS)。它从一个初始状态开始,逐步尝试所有可能的选择,沿着一条路径尽可能深入地搜索。在搜索过程中,一旦发现当前的选择无法得到有效的解或者不符合问题的约束条件,就放弃当前的路径,回溯到上一个状态,然后尝试其他的选择。 以下是回溯法的一般实现步骤(以求解一个特定问题为例): 1. **定义递归函数**:用于进行深度优先搜索。函数通常需要包含当前状态、当前步骤等参数。 2. **终止条件判断**:在递归函数中,首先判断当前状态是否满足终止条件。如果满足,则记录解或者进行其他处理,并返回。 3. **选择列表遍历**:如果不满足终止条件,遍历当前状态下的所有可能选择。 4. **做出选择**:对于每个可能的选择,将其应用到当前状态上,然后递归调用函数,继续搜索新的状态。 5. **撤销选择**:如果递归调用返回后,没有找到有效的解,需要撤销之前做出的选择,恢复到上一个状态,以便尝试其他的选择。 下面是一个用Python实现的简单回溯法示例,用于解决全排列问题: ```python def permute(nums): result = [] def backtrack(path, used): # 终止条件:当路径的长度等于输入列表的长度时,说明已经找到了一个全排列 if len(path) == len(nums): result.append(path[:]) return # 遍历所有可能的选择 for i in range(len(nums)): # 如果该元素已经在路径中,跳过 if used[i]: continue # 做出选择 path.append(nums[i]) used[i] = True # 递归调用 backtrack(path, used) # 撤销选择 path.pop() used[i] = False backtrack([], [False] * len(nums)) return result # 示例使用 nums = [1, 2, 3] print(permute(nums)) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

comli_cn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值