zoukankan      html  css  js  c++  java
  • 回溯算法

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

    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(),这种结构一旦满足条件则返回.要学会这种技巧.可参见236二叉树的最近公共祖先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

  • 相关阅读:
    UVa 1451 Average (斜率优化)
    POJ 1160 Post Office (四边形不等式优化DP)
    HDU 3507 Print Article (斜率DP)
    LightOJ 1427 Substring Frequency (II) (AC自动机)
    UVa 10245 The Closest Pair Problem (分治)
    POJ 1741 Tree (树分治)
    HDU 3487 Play with Chain (Splay)
    POJ 2828 Buy Tickets (线段树)
    HDU 3723 Delta Wave (高精度+calelan数)
    UVa 1625 Color Length (DP)
  • 原文地址:https://www.cnblogs.com/xxswkl/p/11934315.html
Copyright © 2011-2022 走看看