39. Combination Sum-回溯法

来源:互联网 发布:河南网络教育 编辑:程序博客网 时间:2024/05/17 08:26

Tags: backTracking(回溯法)

   http://blog.csdn.net/jarvischu/article/details/16067319   

           http://www.cnblogs.com/chinazhangjie/archive/2010/10/22/1858410.html 

           http://blog.csdn.net/chinajane163/article/details/48969353

具有限界函数的深度优先生成法称为回溯法。(回溯法 = 穷举 + 剪枝)

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

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

        问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。

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

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

二. 回溯法实现 - 递归和递推(迭代)                               

        回溯法的实现方法有两种:递归和递推(也称迭代)。一般来说,一个问题两种方法都可以实现,只是在算法效率和设计复杂度上有区别。
      【类比于图深度遍历的递归实现和非递归(递推)实现】

1. 递归

        思路简单,设计容易,但效率低,其设计范式如下:

[cpp] view plain copy
  1. //针对N叉树的递归回溯方法  
  2. void backtrack (int t)  
  3. {  
  4. if (t>n) output(x); //叶子节点,输出结果,x是可行解  
  5. else  
  6. for i = 1 to k//当前节点的所有子节点  
  7. {  
  8. x[t]=value(i); //每个子节点的值赋值给x  
  9. //满足约束条件和限界条件  
  10. if (constraint(t)&&bound(t))   
  11. backtrack(t+1);  //递归下一层  
  12. }  
  13. } 

2. 递推

      算法设计相对复杂,但效率高。

[cpp] view plain copy
  1. //针对N叉树的迭代回溯方法  
  2. void iterativeBacktrack ()  
  3. {  
  4.     int t=1;  
  5.     while (t>0) {  
  6.         if(ExistSubNode(t)) //当前节点的存在子节点  
  7.         {  
  8.             for i = 1 to k  //遍历当前节点的所有子节点  
  9.             {  
  10.                 x[t]=value(i);//每个子节点的值赋值给x  
  11.                 if (constraint(t)&&bound(t))//满足约束条件和限界条件   
  12.                 {  
  13.                     //solution表示在节点t处得到了一个解  
  14.                     if (solution(t)) output(x);//得到问题的一个可行解,输出  
  15.                     else t++;//没有得到解,继续向下搜索  
  16.                 }  
  17.             }  
  18.         }  
  19.         else //不存在子节点,返回上一层  
  20.         {  
  21.             t--;  
  22.         }  
  23.     }  
  24. }  

三. 子集树和排列树                                                        

1. 子集树

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

       回溯法搜索子集树的算法范式如下:

[cpp] view plain copy
  1. void backtrack (int t)  
  2. {  
  3.   if (t>n) output(x);  
  4.     else  
  5.       for (int i=0;i<=1;i++) {  
  6.         x[t]=i;  
  7.         if (constraint(t)&&bound(t)) backtrack(t+1);  
  8.       }  
  9. }<span style="font-family:SimHei;">  
  10. </span>  

2. 排列树

      所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
      回溯法搜索排列树的算法范式如下:

[cpp] view plain copy
  1. <span style="font-size:18px;">void backtrack (int t)  
  2. {  
  3.   if (t>n) output(x);  
  4.     else  
  5.       for (int i=t;i<=n;i++) {  
  6.         swap(x[t], x[i]);  
  7.         if (constraint(t)&&bound(t)) backtrack(t+1);  
  8.         swap(x[t], x[i]);  
  9.       }  
  10. } </span><span style="font-family:SimHei;font-size:24px;">  
  11. </span>  

四. 经典问题                                    

(1)装载问题
(2)0-1背包问题
(3)旅行售货员问题
(4)八皇后问题
(5)迷宫问题
(6)图的m着色问题

本题的代码:

class Solution {public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {vector<vector<int>> res;vector<int> preCom;int size = candidates.size();backTracking(candidates, target, 0, preCom, res, size, 0);return res;}void backTracking(vector<int>& candidates, int target, int preSum, vector<int>& preCom, vector<vector<int>>& res, int& size, int ind){for (int i = ind; i < size; ++i){int tmp = preSum + candidates[i];if (tmp <= target){preCom.push_back(candidates[i]);if (tmp == target)res.push_back(preCom);if (tmp < target)backTracking(candidates, target, tmp, preCom, res, size, i);preCom.pop_back();}}}};



0 0