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

  • 相关阅读:
    调用外部程序主窗体做子窗体
    查看window编码
    c# 数据库更新和界面刷新的问题
    c# datagridview代码(网上的)
    winform DataGridView控件的打印
    西电ubuntu更新软件源
    C++ primer 学习笔记(2):函数
    C++ Primer 学习笔记(1)——迭代器,数组
    查询表属于哪个数据
    oracle 实用语句
  • 原文地址:https://www.cnblogs.com/xxswkl/p/11934315.html
Copyright © 2011-2022 走看看