• 递归、回溯-算法框架


    之前已经学习过回溯法的一些问题,从这篇文章开始,继续深入学习一下回溯法以及其他经典问题。

    回溯法有通用的解题法之称。用它可以系统的搜索一个问题的所有解或任一解,回溯法是一个既带有系统性又带有跳跃性的搜索算法。

    它的问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先策略搜索。回溯法求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。回溯法求问题的一个解时,只要搜索到问题的一个解就可结束。

    这种以深度优先方式搜索问题解的算法称为回溯法,它适用于解组合数较大的问题。

    回溯法的算法框架:

    1、问题的解空间

    用回溯法解问题时,应明确定义问题的解空间。问题的解空间至少应包含问题的一个(最优解)。

    2、回溯法的基本思想

    确定了解空间的组织结构后,回溯法从开始结点出发,以深度优先方式搜索整个解空间。这个开始结点称为活结点,同时也称为当期那的扩展结点,如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就称为死结点。此时,应往回移动(回溯)至最近的一个或活结点处,并使这个活结点称为当前的扩展结点。回溯法以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已无活结点时为止。

    3、递归回溯 

    回溯法对解空间作深度优先搜索,因此在一般情况下可用递归函数来实现回溯法如下:

    void Backtrack(int t)
    {
        if(t > n)                             //t>n时已搜索到一个叶结点,output(x)得到的可行解x进行记录或输出处理
            Output(x);
        else                                  //当前拓展结点是解空间树的内部结点
        {
            for(int i = f(n,t); i <= g(n, t); i++)   //函数f和g分别表示当前扩展结点处未搜索子树的起止编号
            {
                x[t] = h(i);                         //h(i)表示在当前扩展结点处x[t]的第i个可选值
                if(Constraint(t) && Bound(t))
                    Backtrack(t+1);
            }                                        //循环结束时,已搜索遍当前扩展结点的所有未搜索子树
        }
    }  

    其中,形式参数t表示递归深度,即当前扩展结点在解空间树中的深度。n用来控制递归深度,当t>n时,算法已搜索到叶结点,此时,由Output(x)记录或输出得到的可行解x。算法BackTrack的for循环中f(n,t)和g(n,t)分别表示在当前扩展结点处未搜索过的子树的起始编号和终止编号。h(i)表示在当前扩展结点处x[t]的第i个可选值。Constraint(t)和Bound(t)表示在当前扩展结点处的约束函数和限界函数。Constraint(t)返回的值为true时,在当前扩展结点处x[1:t]的取值满足问题的约束条件,否则不满足问题的约束条件,可剪去相应的子树。

    Bound(t)返回的值为true时,在当前扩展结点处x[1:t]的取值未使目标函数越界,还需由Backtrack(t+1)对其相应的子树做进一步搜索。

    否则,当前扩展结点处x[1:t]的取值使目标函数越界,可剪去相应的子树。执行了算法的for循环后,已搜索遍当前扩展结点的所有未搜索过的子树。Backtrack(t)执行完毕,返回t-1层继续执行,对还没有测试过的x[t-1]的值继续搜索。当t=1时,若已测试完x[1]的所有可选值,外层调用就全部结束。显然,这一搜索过程按深度优先方式进行,调用一次Backtrack(1)即可完成整个回溯搜索过程。

    4、迭代回溯

    采用树的非递归深度优先遍历算法,也可将回溯法表示为一个非递归的迭代过程如下:

    void IterativeBacktrack()
    {
        int t;
     
        t = 1;                                       //当前扩展结点在解空间树中的深度,在这一层确定解向量的第t个分量x[t]的取值
        while(t > 0)
        {
            if(f(n,t) <= g(n,t))                    //f和g分别表示在当前扩展结点处未搜索子树的起止编号
            {
                for(int i = f(n,t); i <= g(n,t); i++)
                {
                    x[t] = h(i);                    //h(i)表示在当前扩展结点处x[t]的第i个可选值
                    if(Constraint(t) && Bound(t))
                    {
                        if(Solution(t))             //solution(t)判断当前扩展结点处是否已得到问题的一个可行解
                            Output(x);
                        else
                            t++;                    //solution(t)为假,则仅得到一个部分解,需继续纵深搜索
                    }
                }
            }
            else
                t--;                                //如果f(n,t)>g(n,t),已搜索遍当前扩展结点的所有未搜索子树,
        }                                           //返回t-1层继续执行,对未测试过的x[t-1]的值继续搜索
    }       

    上述迭代回溯算法中,用Solution(t)判断在当前扩展结点处是否已得到问题的可行解。它返回的值为true时,在当前扩展结点处x[1:t]是问题的可行解。此时,由Output(x)记录或输出得到的可行解。它返回的值为false时,在当前扩展结点处x[1:t]只是问题的部分解,还需向纵深方向继续搜索。

    算法中f(n,t)和g(n,t)分别表示在当前扩展结点处未搜索过的子树的起始编号和终止编号。h(i)表示在当前扩展结点处x[t]的第i个可选值。Constraint(t)和Bound(t)是当前扩展结点处的约束函数和限界函数。Constraint(t)的返回的值为true时,在当前扩展结点处x[1:t]的取值满足问题的约束条件,否则不满足问题的约束条件,可剪去相应的子树。Bound(t)返回的值为true时,在当前扩展结点处x[1:t]的取值未使目标函数越界,还需对其相应的子树做进一步搜索。否则,当前扩展结点处x[1:t]的取值已使目标函数越界,可剪去相应的子树。算法的while循环结束后,完成整个回溯搜索过程。

    5、字集树与排列树

    当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间数称为子集树。这类子集树通常有个叶结点,其结点总个数为.遍历子集树的任何算法均需的计算时间。

    void Backtrack(int t)
    {
        if(t > n)
            Output(x);
        else
        {
            for(int i = 0; i <= 1; i++)
            {
                x[t] = i;
                if(Constraint(t) && Bound(t))
                    Backtrack(t+1);
            }
        }
    }

    当所给的问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。排列树通常有n!个叶结点。因此遍历排列数需要Omega (n!)的计算时间。

    void Backtrack(int t)
    {
        if(t > n)
            Output(x);
        else
        {
            for(int i = t; i <= n; i++)
            {
                swap(x[t], x[i]);
                if(Constraint(t) && Bound(t))
                    Backtrack(t+1);
                swap(x[t], x[i]);
            }
        }
    }

    在调用Backtrack(1)执行回溯搜索之前,先将变量数组x初始化为单位排列(1,2,....,n)

  • 相关阅读:
    docker 安装 clickhouse单机版
    CockRoachDB简介
    Ubuntu18.04 LTS Cockroach集群搭建
    ClickHouse 的一些优化参数
    ClickHouse 概念整理
    OOM Killer机制
    win10系统下载地址
    Quartz.Net在C#中的使用
    JavaScript的undefined与null、NaN的区别
    Java Web基础回顾 —JSP
  • 原文地址:https://www.cnblogs.com/yfr2zaz/p/10569119.html
Copyright © 2020-2023  润新知