【算法Part2】回溯算法
一.概念
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
二.基本思想
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
- 算法在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。
- 回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。
- 回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。(摘自回溯算法)
三.解题步骤
- 针对所给问题,确定问题的解空间:
首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解 - 确定结点的扩展搜索规则
- 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
四.算法框架
bool finished = FALSE; /* 是否获得全部解? */
backtrack(int a[], int k, data input)
{
int c[MAXCANDIDATES]; /*这次搜索的候选 */
int ncandidates; /* 候选数目 */
int i; /* counter */
if (is_a_solution(a,k,input))
process_solution(a,k,input);
else {
k = k+1;
construct_candidates(a,k,input,c,&ncandidates);
for (i=0; i<ncandidates; i++) {
a[k] = c[i];
make_move(a,k,input);
backtrack(a,k,input);
unmake_move(a,k,input);
if (finished) return; /* 如果符合终止条件就提前退出 */
}
}
}
变量解释:
- a[]表示当前获得的部分解;
- k表示搜索深度;
- input表示用于传递的更多的参数;
- is_a_solution(a,k,input)判断当前的部分解向量a[1...k]是否是一个符合条件的解
- construct_candidates(a,k,input,c,ncandidates)根据目前状态,构造这一步可能的选择,存入c[]数组,其长度存入ncandidates
- process_solution(a,k,input)对于符合条件的解进行处理,通常是输出、计数等
- make_move(a,k,input)和unmake_move(a,k,input),前者将采取的选择更新到原始数据结构上,后者把这一行为撤销。
五.题型解答
1. 求一个集合的所有子集(leetcode.78)
//给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrace(vector<int>& nums,int start){
res.push_back(temp);
for(int i = start;i < nums.size();++i){
temp.push_back(nums[i]);
backtrace(nums,i+1);
temp.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
if(!nums.size())
return res;
backtrace(nums,0);
return res;
}
};
2.电话号码的字母组合(leetcode.17)
/*
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
*/
class Solution {
public:
map<char, string> mp = { { '2', "abc" }, { '3', "def" }, { '4', "ghi" }, { '5', "jkl" },
{ '6', "mno" }, { '7', "pqrs" }, { '8', "tuv" }, { '9', "wxyz" } };
vector<string> res;
string temp;
void backtrace(string digits, int start){
if (!digits.size())
res.push_back(temp);
else{
char num = digits[0];
string letter = mp[num];
for (int i = 0; i<letter.size(); i++){
temp.push_back(letter[i]);
backtrace(digits.substr(1), i + 1);
temp.pop_back();
}
}
}
vector<string> letterCombinations(string digits) {
if (!digits.size())
return res;
backtrace(digits, 0);
return res;
}
};