1. 程式人生 > >39. Combination Sum-回溯法

39. Combination Sum-回溯法

Tags: backTracking(回溯法)

具有限界函式的深度優先生成法稱為回溯法。(回溯法 = 窮舉 + 剪枝)

回溯法按深度優先策略搜尋問題的解空間樹。首先從根節點出發搜尋解空間樹,當演算法搜尋至解空間樹的某一節點時,先利用剪枝函式判斷該節點是否可行(即能得到問題的解)。如果不可行,則跳過對該節點為根的子樹的搜尋,逐層向其祖先節點回溯;否則,進入該子樹,繼續按深度優先策略搜尋。

        回溯法的基本行為是搜尋,搜尋過程使用剪枝函式來為了避免無效的搜尋。剪枝函式包括兩類:1. 使用約束函式,剪去不滿足約束條件的路徑;2.使用限界函式,剪去不能得到最優解的路徑。

        問題的關鍵在於如何定義問題的解空間,轉化成樹(即解空間樹)。

解空間樹分為兩種:子集樹和排列樹。兩種在演算法結構和思路上大體相同。

       當問題是要求滿足某種性質(約束條件)的所有解或最優解時,往往使用回溯法。

       它有“通用解題法”之美譽。

二. 回溯法實現 - 遞迴和遞推(迭代)                               

        回溯法的實現方法有兩種:遞迴和遞推(也稱迭代)。一般來說,一個問題兩種方法都可以實現,只是在演算法效率和設計複雜度上有區別。
      【類比於圖深度遍歷的遞迴實現和非遞迴(遞推)實現】

1. 遞迴

        思路簡單,設計容易,但效率低,其設計正規化如下:
  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. 遞推

      演算法設計相對複雜,但效率高。
  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揹包問題,從所給重量、價值不同的物品中挑選幾個物品放入揹包,使得在滿足揹包不超重的情況下,揹包內物品價值最大。它的解空間就是一個典型的子集樹。

       回溯法搜尋子集樹的演算法正規化如下:

  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個元素滿足某種性質的排列時,相應的解空間就是排列樹。
如旅行售貨員問題,一個售貨員把幾個城市旅行一遍,要求走的路程最小。它的解就是幾個城市的排列,解空間就是排列樹。
      回溯法搜尋排列樹的演算法正規化如下:
  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();
			}
		}
	}
};