回溯算法

回溯法-深度优先算法

 

一、概述

回溯算法是把问题的解空间转化成了图或树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解和最优解。

基本思想类似于

图的深度优先搜索

二叉树的后续遍历

 

【分支限界法:广度优先搜索。思想类似于图的广度优先遍历。二叉树的层序遍历。】

 

详细描述:

    首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不行,则跳过对该节点为根的子树的搜素,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。

    回溯法的基本行为是搜索,搜素过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1.使用约束函数,减去不满足

 

回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。

 

回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。

 

 

 回溯法应用:

     当问题是要求满足某种性质(约束条件)的所有解或最优解时,往往使用回溯法。

     它有“通用解题法”之美誉。

 

回溯算法实现:

   回溯算法有递归和递推

   递归思路简单,设计容易,但效率低

递归函数模板如下:
void BackTrace(int t) {
    if(t>n)
        Output(x);
    else
        for(int i = f (n, t); i <= g (n, t); i++ ) {
            x[t] = h(i);
            if(Constraint(t) && Bound (t))
                BackTrace(t+1);
        }
}
 
采用迭代的方式也可实现回溯算法,迭代回溯算法的模板如下:
void IterativeBackTrace(void) {
  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)) {
          if ( Solution(t))
            Output(x);
          else
            t++;
        }
      }
    else t− −;
  }
}

子集树

        所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。
如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。

      该类树通常有2n个叶子结点,总结点数为2n+1- 1,遍历子集树的任何算法需要的计算时间复杂度均为O(2n)。

回溯法搜索子集树的一般算法描述如下:
void BackTrace(int t) {
  if(t>n)
    Output(x);
  else
    for(int i = 0; i <= n; i++) {
      x[t] = i;
      if(Contraint(t) && Bound(t))
        BackTrace (t + 1);
    }
}
排列树
所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
排列树通常有n! 个叶子结点,遍历排列树需要的计算时间复杂度为O(n!)。
回溯法搜索排列树的算法模板如下:
void BackTrace(int t) {
  if(t>n)
    Output(x);
  else
  for(int i = 0; i <= n; i++) {
    Swap(x[t], x[i]);
    if(Contraint (t) && Bound (t))
      BackTrace(t + 1);
    Swap(x[t], x[i]);
  }
}

    

回溯算法最后一行一般是撤销,为什么要撤销选择,是因为分支污染问题,因为list是引用传递,当从一个分支跳到另一个分支的时候,如果不把前一个分支的数据给移除掉,那么list就会把前一个分支的数据带到下一个分支去,造成结果错误。那么除了把前一个分支的数据给移除以外还有一种方式就是每个分支都创建一个list,这样每个分支都是一个新的list,都不互相干扰

 

 

posted @ 2021-08-16 18:51  diameter  阅读(525)  评论(0编辑  收藏  举报