回溯演算法初涉
阿新 • • 發佈:2018-11-29
什麼是回溯演算法?
回溯演算法也是深度搜索演算法(DFS),也是遞迴。
回溯演算法是最基本的暴力解決演算法,可以很好的解決大多數問題,由此我們需要掌握它。
遞迴有兩點要素:
1. 遞迴邊界。
2. 遞迴的邏輯——遞迴"公式"。
遞迴邊界即是需要我們自己尋找的程式出口,這是遞迴的終點,它將返回子問題的解。
遞迴公式則理解為對於問題的合理分解。即將一個大問題,合理的將之簡化為子問題。也就是說將一個複雜的系統通過合理的方式拆解為單位動作。我們需要研究的也就是如何合理分解。
下面將給出一個遞迴函式框架(並不是固定不變的,會根據問題的變化而改動),可以進行適當的理解。
返回型別 dfs(形參1,形參2,形參3,...,形參n){
if (遞迴邊界){
return 子問題的解
}
通過合理的方式拆解問題
}
下面舉幾個例子。
71.子集
問題:
給定一組不含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3] 輸出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
思路:
這道題要我們返回陣列所有的子集(不包含重複子集)。我們將之拆分:
1. 將輸出分為三部分,分別輸入1位,2位,3位,...,n位。
2. 通過迴圈,將陣列中的後面的數字逐個推進棧中。
3. 建立遞迴邊界,即為判斷容器 vector 中的數字達到 k 值沒有。
程式:
class Solution { public: vector<vector<int>> res; vector<int> tmpv; vector<vector<int>> subsets(vector<int>& nums) { for (int k=0;k<=nums.size();k++) // 將輸出分為n部分 dfs(nums,0,k); return res; } void dfs(vector<int> num, int i,int k){ if (i == k) // 判斷是否與輸出數字量相同 res.push_back(tmpv); for (int j=i;j<k;j++){ // 迴圈 tmpv.push_back(num[j]); // 推入棧中 dfs(num,j+1,k); // 遞迴,加入下一個數 tmpv.pop_back(); // 返回原來的狀態 } } };
46. 全排列
題目:
給定一個沒有重複數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3] 輸出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
思路:
題目讓我們返回給定序列所有的排列組合。
由此我們開始拆解問題,將之分成多個子問題:即兩個交換元素(當只有兩個元素時,交換一次即可。)。
從第一個元素開始作為交換點,不斷與後續元素交換,得到新的序列。返回狀態,再從第二個元素開始。。。
程式:
class Solution {
public:
vector<vector<int>> res;
vector<int> tmpv;
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums,0);
return res;
}
void dfs(vector<int> &nums,int i){
if (i == nums.size()) //
res.push_back(nums);
for (int j=i;j<nums.size();j++){
swap(nums[j],nums[i]);
dfs(nums,i+1);
swap(nums[j],nums[i]);
}
}
};