39. Combination Sum-回溯法
阿新 • • 發佈:2019-02-10
Tags: backTracking(回溯法)
具有限界函式的深度優先生成法稱為回溯法。(回溯法 = 窮舉 + 剪枝)
回溯法按深度優先策略搜尋問題的解空間樹。首先從根節點出發搜尋解空間樹,當演算法搜尋至解空間樹的某一節點時,先利用剪枝函式判斷該節點是否可行(即能得到問題的解)。如果不可行,則跳過對該節點為根的子樹的搜尋,逐層向其祖先節點回溯;否則,進入該子樹,繼續按深度優先策略搜尋。
回溯法的基本行為是搜尋,搜尋過程使用剪枝函式來為了避免無效的搜尋。剪枝函式包括兩類:1. 使用約束函式,剪去不滿足約束條件的路徑;2.使用限界函式,剪去不能得到最優解的路徑。
問題的關鍵在於如何定義問題的解空間,轉化成樹(即解空間樹)。
當問題是要求滿足某種性質(約束條件)的所有解或最優解時,往往使用回溯法。
它有“通用解題法”之美譽。
二. 回溯法實現 - 遞迴和遞推(迭代)
回溯法的實現方法有兩種:遞迴和遞推(也稱迭代)。一般來說,一個問題兩種方法都可以實現,只是在演算法效率和設計複雜度上有區別。【類比於圖深度遍歷的遞迴實現和非遞迴(遞推)實現】
1. 遞迴
思路簡單,設計容易,但效率低,其設計正規化如下:-
//針對N叉樹的遞歸回溯方法
- void backtrack (int t)
- {
- if (t>n) output(x); //葉子節點,輸出結果,x是可行解
- else
- for i = 1 to k//當前節點的所有子節點
- {
- x[t]=value(i); //每個子節點的值賦值給x
- //滿足約束條件和限界條件
- if (constraint(t)&&bound(t))
- backtrack(t+1); //遞迴下一層
- }
- }
2. 遞推
演算法設計相對複雜,但效率高。- //針對N叉樹的迭代回溯方法
- void iterativeBacktrack ()
- {
-
int t=1;
- while (t>0) {
- if(ExistSubNode(t)) //當前節點的存在子節點
- {
- for i = 1 to k //遍歷當前節點的所有子節點
- {
- x[t]=value(i);//每個子節點的值賦值給x
- if (constraint(t)&&bound(t))//滿足約束條件和限界條件
- {
- //solution表示在節點t處得到了一個解
- if (solution(t)) output(x);//得到問題的一個可行解,輸出
- else t++;//沒有得到解,繼續向下搜尋
- }
- }
- }
- else//不存在子節點,返回上一層
- {
- t--;
- }
- }
- }
三. 子集樹和排列樹
1. 子集樹
所給的問題是從n個元素的集合S中找出滿足某種性質的子集時,相應的解空間成為子集樹。如0-1揹包問題,從所給重量、價值不同的物品中挑選幾個物品放入揹包,使得在滿足揹包不超重的情況下,揹包內物品價值最大。它的解空間就是一個典型的子集樹。
回溯法搜尋子集樹的演算法正規化如下:
- void backtrack (int t)
- {
- if (t>n) output(x);
- else
- for (int i=0;i<=1;i++) {
- x[t]=i;
- if (constraint(t)&&bound(t)) backtrack(t+1);
- }
- }<span style="font-family:SimHei;">
- </span>
2. 排列樹
所給的問題是確定n個元素滿足某種性質的排列時,相應的解空間就是排列樹。如旅行售貨員問題,一個售貨員把幾個城市旅行一遍,要求走的路程最小。它的解就是幾個城市的排列,解空間就是排列樹。
回溯法搜尋排列樹的演算法正規化如下:
- <span style="font-size:18px;">void backtrack (int t)
- {
- if (t>n) output(x);
- else
- for (int i=t;i<=n;i++) {
- swap(x[t], x[i]);
- if (constraint(t)&&bound(t)) backtrack(t+1);
- swap(x[t], x[i]);
- }
- } </span><span style="font-family:SimHei;font-size:24px;">
- </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();
}
}
}
};