背包问题之回溯法

时间:2021-01-24 04:25:17

问题描述:背包的容量为C,现有N件物品,价格分别为p[0],p[1]......p[n-1].重量分别为:w[0],w[1]......w[n-1].从N件物品中选择任意个放入背包中,使得物体的价值最大并且总重量不超过背包的容量C。

       采用数学语言描述如下:

      在  w[0]*x[0] + w[1] *x[1]+....... +w[n-1]*x[n-1]  < C, x[i] = 0 或1 的条件下

      求  p[0]*x[0] + p[1] *x[1]+....... +p[n-1]*x[n-1]   的最大值。

 

 

回溯法类其实也算枚举法的一种,但在搜索过程中,一般使用递归来完成。

 

回溯法的基本思想

对于用回溯法求解的问题,首先要将问题进行适当的转化,得出状态空间树。 这棵树的每条完整路径都代表了一种解的可能。通过深度优先搜索这棵树,枚举每种可能的解的情况;从而得出结果。但是,回溯法中通过构造约束函数,可以大大 提升程序效率,因为在深度优先搜索的过程中,不断的将每个解(并不一定是完整的,事实上这也就是构造约束函数的意义所在)与约束函数进行对照从而删除一些 不可能的解,这样就不必继续把解的剩余部分列出从而节省部分时间。

回溯法中,首先需要明确下面三个概念:

(一)约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。

(二)状态空间树:刚刚已经提到,状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。

(三)扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。

 

利用回溯法解题的具体步骤

首先,要通过读题完成下面三个步骤:

(1)描述解的形式,定义一个解空间,它包含问题的所有解。

(2)构造状态空间树。

(3)构造约束函数(用于杀死节点)。

 

然后就要通过DFS思想完成回溯,伪代码如下:

 

void BackTrack(int depth)

{

        if(depth > maxDepth) //已经到最大深度

        {

              if(solution is target)

                      save solution;

              return;

         }

         for (int i =0;i<TotalExpendNode;++i)

         {

               if(currentNode is searchable) //当前结点满足约束条件

               {

                      do something;

                      BackTrack(depth+1);

                      undo something;

                }

              

         }

         

}

 

 

对于背包问题其算法代码如下:

 

 

 

 

小结:回溯法作为一种穷举方法,可以使用约束函数来排除一些不可能的结点。虽然不理论上此算法在理论上最坏的情况下,复杂度仍为2^n,但在实际实验中,其搜索效率比上一次使用的2进制枚举要高很多。