回溯算法

回溯算法的应用有两个关键点:

1 要选择好剪枝条件,即什么时候进行回溯.

2 要控制好传递的参数,回溯函数每次返回来的时候,仍然是原来的值,不要随机更改值,常用的技巧是在函数传参的时候顺便修改参数的值,此外如果要增加限制条件的时候,可以在回溯函数中增加参数.回溯函数每次回溯结束的时候就会把以前的内存释放掉

一 回溯算法模板

回溯算法要注意三个要点

a. 利用深度优先遍历时的结束条件,如果满足结束条件了,则将生成好的数据存储,结束本次回溯,否则继续回溯

b. 状态恢复,即每次调用回溯函数backtrack()结束的时候,要返回调用前的状态.

c. 合理的剪枝条件,画树状图的时候就判断出哪些分支不需要,及增加什么条件即可以把该枝去掉.

技巧:

1 对于有的回溯算法如力扣60题第K个排列,回溯的过程中一旦满足条件就需要停止,这时候就要把回溯函数写到return后面,最下层的回溯函数中的if len(a) == n: return a条件一旦满足了,回溯函数就会逐层将a返回.否则的话由于每次函数调用的时候都会压栈出栈,出栈的时候会恢复以前的状态,这时候需要定义全局变量来记录回溯函数调用的次数,非常繁琐,而用return的话很简洁.

2 对于有些for循环的剪枝情况,要会用continue来结束一次for循环,不要只会用break,直接把整个for循环都结束了.

3 想要选好的剪枝条件,必须要先选几个典型的数据,亲自动手画出树状图,通过树状图来观察出哪些枝需要剪掉,再归纳出对应的剪枝条件,把条件加入程序中.

4 对于可变对象,压栈完成后一定要记得出栈,比如a.append(k) a.pop(),否则很容易出错,特别是在同级的for循环中,

对于可变对象,如果是回溯函数直接传参的时候修改的,且变量名相同(局部变量和全局变量),则回溯函数结束调用后会自动恢复之前的值.

def fun1(x):
    x.append([6,7])
    x[0] = 999
    y[0] = 999
    return
def fun2(x1):
    x.append([6,7])
    x[0] = 999
    x1.append([6,7])
    x1[0] = 999
    return
y = [5,5,5]
x = [1,2,3]
# 这里x y都是可变对象,但是y的值被修改了但x的值却没有变,原因是fun1中的局部变量的名字是x,
# 恰恰和全局变量相同了,这时候fun1中的x都认为是局部变量了,所以修改x中的值后,函数结束调用后就会释放掉内存,所以x的值不会被修改
# 而同样的全局变量y便被修改了
fun1(x+[4,5])
print('x的值没有变:',x)
print('y的值变了:', y)
# 这次全局变量x的值被修改了,可见如果在回溯函数中用fun1中的方法,则每次调用结束后,会自动恢复前面的值,
fun2(x+[8,9])
print('x的值变了', x)
# x的值没有变: [1, 2, 3]
# y的值变了: [999, 5, 5]
# x的值变了 [999, 2, 3, [6, 7]]
View Code

5 回溯函数对结果的记录有两种常用方法,一是用全局变量的list,直接修改里面的值.二是每次都返回值,在一个for循环结束的时候求和.这个方便用lru_cache装饰器来减少运算次数.可参见576出界路径数

6 利用回溯法的时候,如果返回值是一旦满足条件则返回True,否则需要遍历完所有的情况,再返回.这时可以利用retrun的技巧,return fun1() or fun2(),这种结构一旦满足条件则返回.要学会这种技巧.可参见1306跳跃游戏III

7 当回溯函数的输入输出有大量的重复时,要用字典或数组或lru_cache来记录,避免重复回溯,会大大节省时间,参见576出界路径数741摘樱桃

模板1: 这个模板适合于处理一维list的回溯问题

def backtrack(a, b=nums):
    回溯结束条件          如果满足了则将生成的结果存储,并return返回,结束函数的调用
    剪枝条件             务必要画出树状图观察归纳来确定合理的剪枝条件      
    for i in nums:      确定nums也很关键,它是每次遍历的元素构成的list,也要通过画树状图来确定
        剪枝条件
        backtrack(a, b=nums)
backtrack()
View Code

模板2: 类似与模板1,适合于处理返回值多的情况.参见37解数独1306跳跃游戏III

def backtrack(a, b=nums):
    回溯结束条件          如果满足了则将生成的结果存储,并return返回,结束函数的调用
    剪枝条件             务必要画出树状图观察归纳来确定合理的剪枝条件 
    确定回溯范围          即确定nums
    当nums为空和回溯结束条件都需要结束函数返回返回值的时候,这个时候需要在for循环中添加判断条件
    for i in nums:      确定nums也很关键,它是每次遍历的元素构成的list,也要通过画树状图来确定
        剪枝条件
        ans = backtrack(a, b=nums)
        if ans is False or ans is None:
            pass
        else:
            return ans
backtrack()
View Code

dedfas

相关推荐