算法基础之回溯与深度优先遍历(一)

176 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

“回溯法”也称“试探法”。它是从问题的某一状态出发,不断“试探”着往前走一步,当一条路走到“尽头”,不能再前进(拓展出新状态)的时候,再倒回一步或者若干步,从另一种可能的状态出发,继续搜索,直到所有的“路径(状态)”都一一试探过。 回溯算法可以看做是深度优先遍历算法的一种。深度优先遍历的目的是“遍历”,本质是无序的。也就是说访问次序不重要,重要的是都被访问过了(需要记录结点是否被visited)。而回溯的目的是“求解过程”,本质是有序的,不同的次序会导致不一样的结果,一般不需要记录结点是否visited) 深度优先搜索可以采用递归(系统栈)和非递归(手工栈)两种方法实现。往往需要记录整颗搜索树。回溯算法一般不用记录整颗搜索树。

1. 回溯算法的三要素

回溯过程其实就是一个决策树的遍历过程。包含三个要素:

  1. 路径:已经做的选择
  2. 选择列表:当前可以做的选择
  3. 结束条件:即决策树的叶子节点,无法再继续往下选择的条件

回溯算法的基本框架可以概括为:

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

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

2. 回溯算法的例子

2.1. 求子集问题

2.1.1. leetcode-78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 说明: 解集不能包含重复的子集。

python代码:

class Solution:
    def backtrack(self,index,decisions,nums,temp_result,results):
        if index==len(nums):
            results.append(temp_result.copy())
            return
        for decision in decisions:
            if decision:
                temp_result.append(nums[index])
                self.backtrack(index+1,decisions,nums,temp_result,results)
                temp_result.pop()
            else:
                self.backtrack(index+1,decisions,nums,temp_result,results)
    def subsets(self, nums: List[int]) -> List[List[int]]:
        decisions=[True, False]
        results=[]
        temp_result=[]
        index=0
        self.backtrack(index,decisions,nums,temp_result,results)
        return results

上述代码中,所谓的“选择”就是当前子集中是否要保留某个元素decisions=[True,False];路径就是当前子集中包含了哪些元素temp_result;结束条件就是当前路径已经遍历到了数组的最后一个元素:index==len(nums);撤销的时候要注意,需要判断当前的选择是否是添加了当前的元素,如果是的话,就撤销该元素,也即最后一个元素(等价于temp_result.pop()

假如输入是nums[1,2,3],则输出是[[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]]。读者可以结合这个输入输出例子来理解代码的运行过程