回溯算法(1)

来源:互联网 发布:信贷规模数据 编辑:程序博客网 时间:2024/06/05 06:05

 搜索与回溯是计算机解题中常用的算法,很多问题无法根据某种确定的计算法则来求解,可以利用搜索与回溯的技术求解。回溯是搜索算法中的一种控制策略

它的基本思想是:

为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。


dd

有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。


回溯法的基本做法是搜索,或是一种组织得井井有条的、能避免不必要搜索的穷举式搜索法这种方法适用于解一些组合数相当大的问题。


回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解:如果


肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。


总结:【深入优先,到任一点,先判断结点是否包含问题的解,yes继续,no回溯到祖先】



回溯算法的框架

一、问题的解空间


对于任何一个问题,可能解的表示方式和它相应的解释隐含了解空间及其大小。


例如,对于有n个物品的0/1背包问题,其可能解的表示方式可以有以下两种:
(1)可能解由一个不等长向量组成,当物品i(1≤i≤n)装入背包时,解向量中包含分量i,否则,解向量中不包含分量i,当n=3时,其解空间是:
       { ( ), (1), (2), (3), (1, 2), (1, 3), (2, 3), (1, 2, 3) }
(2)可能解由一个等长向量{x1, x2, …, xn}组成,其中xi=1(1≤i≤n)表示物品i装入背包,xi=0表示物品i没有装入背包,当n=3时,其解空间是:
    {(0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 1, 1) }



问题的解向量:回溯法希望一个问题的解能够表示成一个n元式(x1,x2,…,xn)的形式。
显约束:对分量xi的取值限定。
隐约束:为满足问题的解而对不同分量之间施加的约束。
解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。


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


为了用回溯法求解一个具有n个输入的问题,一般情况下,将其可能解表示为满足某个约束条件的等长向量X=(x1, x2, …, xn),其中分量xi (1≤i≤n)的取值范围是某个有限集合Si={ai1, ai2, …, airi},所有可能的解向量构成了问题的解空间。 


 问题的解空间一般用解空间树(Solution Space Trees,也称状态空间树)的方式组织,树的根结点位于第1层,表示搜索的初始状态,第2层的结点表示对解向量的第一个分量做出选择后到达的状态,第1层到第2层的边上标出对第一个分量选择的结果,依此类推,从树的根结点到叶子结点的路径就构成了解空间的一个可能解。


对于n=3的0/1背包问题,其解空间树如下图所示,树中的8个叶子结点分别代表该问题的8个可能解


cc


二、回溯法的基本思想


回溯法从根结点出发,按照深度优先策略搜索(遍历)解空间树,搜索满足约束条件的解。


初始时,根结点成为一个活结点,同时也称为当前的扩展结点。在当前扩展结点处,搜索向纵深方向移至一个新结点。这个新结点成为一个新的活结点,并成为当前的扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为一个死结点(即不再是一个活节点)。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。




hh

在搜索至树中任一结点时,先判断该结点对应的部分解是否满足约束条件,或者是否超出限界函数的界,也就是判断该结点是否包含问题的(最优)解,如果肯定不包含,则跳过对以该结点为根的子树的搜索,即所谓剪枝(Pruning);否则,进入以该结点为根的子树,继续按照深度优先策略搜索。
在搜索过程中,通常采用两种策略避免无效搜索:

(1)用约束条件剪去得不到可行解的子树;
(2)用限界函数剪去得不到最优解的子树。
  这两类函数统称为剪枝函数(Pruning Function)。

hh

总结:这个例子我们可以看到,使用了约束条件和限定函数的方法,约束条件cr<wi,限定函数v<=当前最大值


回溯法是一种通用性解法,可以将回溯法看作是带优化的穷举法。

回溯法的基本思想是在一棵含有问题全部可能解的状态空间树上进行深度优先搜索,解为叶子结点,搜索过程中,每到达一个结点时,则判断该结点为根的子

树是否含有问题的解,如果不含有问题的解,则放弃对该子树的搜索,退回到上层父结点,继续下一步深度优先搜索过程。

在回溯法中,并不是先构造出整棵状态空间树,再进行搜索,而是在搜索过程,逐步构造出状态空间树,即边搜索,边构造


三、递归回溯


void backtrack (int t) 
//t表示递归深度,即对解空间树的第t层节点进行深度优先搜索;
{
       if (t>n) output(x); //n表示解空间树的高度;
       //大于这个深度没有意义了,解的高度就是如此
       else
         for (int i=f(n,t);i<=g(n,t);i++) 
         {
           //f(n,t)和g(n,t)分别表示在当前扩展结点处未搜索过的子树的起始编号和终止编号;
  
           x[t]=h(i);
           //h(i)表示在当前扩展结点处x[t]的第i个可选值;
           if (constraint(t)&&bound(t)) 
           backtrack(t+1);//递归
           // constraint(t)和bound(t)分别表示在当前扩展结点处的约束函数和限界函数;
           }
}




四、迭代回溯

void IterativeBacktrack ()
{
  int t=1;
  while (t>0) {
    if (f(n,t)<=g(n,t)) //还有子树未搜索
      for (int i=f(n,t);i<=g(n,t);i++) {
        x[t]=h(i);
        if (constraint(t)&&bound(t)) {
//函数solution(t)判断在当前扩展结点处是否找到问题的一个可行解;
          if (solution(t)) output(x);//找到解就输出
          else t++;}
        }
    else t--;
    }
}



五、子集树与排列树

当所给问题是从n个元素的集合S中找出满足某种性质的子集时,解空间为子集树。 


当所给问题是从n个元素的集合S中找出满足某种性质的排列时,解空间为排列树


ff

六.回溯法的求解过程

(1)针对所给问题,定义问题的解空间
(2)确定易于搜索的解空间结构
(3) 深度优先搜索解空间,并在搜索中用剪枝函数避免无效搜索。


常用剪枝函数:
用约束函数在扩展结点处剪去不满足约束的子树;
用限界函数剪去得不到最优解的子树。


需要注意的是,问题的解空间树是虚拟的并不需要在算法运行时构造一棵真正的树结构。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2h(n))或O(h(n)!)内存空间。


0 0