码迷,mamicode.com
首页 > 其他好文 > 详细

回溯法

时间:2015-04-06 15:48:22      阅读:254      评论:0      收藏:0      [点我收藏+]

标签:c++   c   算法   数据结构   解决方案   

1、概念

      回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

   回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

     许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

2、基本思想

   在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

       若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。

       而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

3、问题的解空间和空间树

        这个空间必须至少包含一个解(可能是最优的)。 一个复杂问题的解决往往由多部分构成,即,一个大的解决方案可以看作是由若干个小的决策组成。很多时候它们构成一个决策序列。解决一个问题的所有可能的决策序列构成该问题的解空间。解空间中满足约束条件的决策序列称为可行解。一般说来,解任何问题都有一个目标,在约束条件下使目标值达到最大(或最小)的可行解称为该问题的最优解。在解空间中,前k项决策已经取定的所有决策序列之集称为k定子解空间。0定子解空间即是该问题的解空间。    

      问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。

    解空间的确定与我们对问题的描述有关。如何组织解空间的结构会直接影响对问题的求解效率。这是因为回溯方法的基本思想是通过搜索解空间来找到问题所要求的解。一般地,可以用一棵树来描述解空间,称为解空间树
       当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树称为子集合树。此时,解空间有技术分享个元素,遍历子集树的任何算法均需的技术分享计算时间。

如例:定和子集问题: 已知一个正实数的集合P= {W1,w2, ... Wn}和另一个正实数M.试求P的所有子集S,使得S中的数之和等于M。这个问题的解可以表

示成0/1数组{x1,x2,…,xn},依据W1是否属于S, X1分别取值1或0。故解空间中共有技术分享个元素。它的树结构是一棵完整二叉树。 

当所给的问题是确定n个元素的满足某种性质的排列时,相应的解空间树称为排列树,此时,解空间有个元素。遍历排列树的任何算法均需的计算时间,均需的技术分享计算时间。

技术分享

我们把这个例子逐一解析:

问题的解向量:问题的解能够表示成一个n元式(x1,x2,…,xn)的形式。

显约束:对分量xi的取值限定。

隐约束:为满足问题的解而对不同分量之间施加的约束。

解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。

注意:同一个问题可以有多种表示,有些表示方法更简单,所需表示的状态空间更小(存储量少,搜索方法简单)。

下面是n=3时的0-1背包问题用完全二叉树表示的解空间:

技术分享


       为了叙述方便,引进一些关于解空间树结构的术语。解空间树上的每个节点确定求解问题的一个问题状态,它由一条从根到该节点的路径描述。由根到所有其它节点的路径描述了这个问题的状态空间。解状态是这样一些问题状态S,对于这些问题状态,由根到S的那条路径确定了解空间的一个元组。即答案状态是这样的一些解状态S,对于这些解状态而言,由根到S的这条路径确定了这个问题的一个解(即可行解),解空间的树结构称为状态空间树

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

       事实上,当我们将问题的有关数据以一定的数据结构存储好以后(例如,旅行商问题存储赋权图的邻接矩阵、定和子集问题是存储已知的n+1个数、4皇后问题用整数对(i,j)表示棋盘上各个位置,不必先建立一个解空间树),就搜索生成解空间树的一部分或全部,并寻找所需要的解。也就是说,对于实际问题不必生成整个状态空间树,然后在整个解空间中搜索,我们只需有选择地搜索。为了使搜索更加有效,常常在搜索过程中加一些判断以决定搜索是否该终止或改变路线。通常采用两种策略来避免无效的搜索,提高回溯法的搜索效率:

其一是使用约束函数,在扩展节点处剪去不满足约束的子树;

其二是用限界函数, “剪去”不能达到最优解的子树。

这两种函数统称为剪枝函数。

总结:

扩展结点:一个正在产生儿子的结点称为扩展结点

活结点:一个自身已生成但其儿子还没有全部生成的节点称做活结点

死结点:一个所有儿子已经产生的结点称做死结点

深度优先的问题状态生成法:如果对一个扩展结点R,一旦产生了它的一个儿子C,就把C当做新的扩展结点。在完成对子树C(以C为根的子树)的穷尽搜索之后,将R重新变成扩展结点,继续生成R的下一个儿子(如果存在)

宽度优先的问题状态生成法:在一个扩展结点变成死结点之前,它一直是扩展结点。

回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(bounding function)来处死(剪枝)那些实际上不可能产生所需解的活结点,以减少问题的计算量。具有限界函数的深度优先生成法称为回溯法。(回溯法 = 穷举 + 剪枝)。

4、回溯法的思路

描述问题:

定义可用回溯法求解的问题P:对于已知的由n元组(x1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。其中Si是分量xi的定义域,且 |Si| 有限,i=1,2,…,n。我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。

解问题P的最朴素的方法就是枚举法,即对E中的所有n元组逐一地检测其是否满足D的全部约束,若满足,则为问题P的一个解。但显然,其计算量是相当大的。

基本思路:

若已有满足约束条件的部分解,不妨设为(x1,x2,x3,……xi),I<n,则添加x(i+1)属于s(i+2),检查 (x1,x2,……,xi,x(i+1))是否满足条件,满足了就继续添加x(i+2)、s(i+2),若所有的x(i+1)属于s(i+1)都不能得到 部分解,就去掉xi,回溯到(xi,x2,……x(i- 1)),添加那些未考察过的x1属于s1,看其是否满足约束条件,为此反复进行,直至得到解或证明无解。

这个回溯法明显提高算法效率。

5.回溯法的步骤

总结起来,运用回溯法解题通常包括以下三个步骤 
1).确定问题的解空间 :针对所给问题,定义问题的解空间; 

 子集树问题:装载问题、符号三角形问题、0-1背包问题、最大团问题
排列树问题:批处理作业调度、n后问题、旅行售货员问题、圆排列问题、电路板排列问题
其他:图的m着色问题

2).确定易于搜索的解空间结构:

找出适当的剪枝函数,约束函数和限界函数。

3).以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效的搜索。

递归回溯

迭代回溯

4)利用限界函数避免移动到不可能产生解的子空间


6、算法框架

     (1)问题框架

      设问题的解是一个n维向量(a1,a2,………,an),约束条件是ai(i=1,2,3,…..,n)之间满足某种条件,记为f(ai)。

  (2)非递归回溯框架

int a[n],i;  
初始化数组a[];  
i = 1;  
while (i>0(有路可走)   and  (未达到目标))  // 还未回溯到头  
{  
     if(i > n)                                              // 搜索到叶结点  
     {     
           搜索到一个解,输出;  
     }  
     else                                                   // 处理第i个元素  
     {   
          a[i]第一个可能的值;  
          while(a[i]在不满足约束条件且在搜索空间内)  
          {  
               a[i]下一个可能的值;  
          }  
          if(a[i]在搜索空间内)  
          {  
               标识占用的资源;  
               i = i+1;                              // 扩展下一个结点  
          }  
         else   
         {  
               清理所占的状态空间;            // 回溯  
               i = i –1;   
         }  
}  

(3)递归的算法框架

         回溯法是对解空间的深度优先搜索,在一般情况下使用递归函数来实现回溯法比较简单,其中i为搜索的深度,框架如下:

int a[n];  
try(int i)  
{  
    if(i>n)  
       输出结果;  
    else  
    {  
       for(j = 下界; j <= 上界; j=j+1)  // 枚举i所有可能的路径  
       {  
            if(fun(j))                 // 满足限界函数和约束条件  
            {  
                a[i] = j;  
              ...                         // 其他操作  
                try(i+1);  
              回溯前的清理工作(如a[i]置空值等);  
              }  
         }  
     }  
}  


回溯法

标签:c++   c   算法   数据结构   解决方案   

原文地址:http://blog.csdn.net/u014082714/article/details/44901657

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!