背景:有时会遇到这样一类题目,它的问题可以分解,但是又不能得出明确的动态规划或是递归解法,此时可以考虑用回溯法解决此类问题。回溯法的优点 在于其程序结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效
率。但是,对于可以得出明显的递推公式迭代求解的问题,还是不要用回溯 法,因为它花费的时间比较长。
回溯法中,首先需要明确下面三个概念:
(一)约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。
(二)状态空间树:刚刚已经提到,状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。
(三)扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。
(一)约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。
(二)状态空间树:刚刚已经提到,状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。
(三)扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。
为什么用DFS
深度优先搜索(DFS)和广度优先搜索(FIFO)
然后用DFS来遍历就可以了,这个与回溯不同的是回溯会加上约束条件,如果条件不满足就返回上一层,而本例则没有约束条件。
在 分支界限法中,一般用的是FIFO或最小耗费搜索;其思想是一次性将一个节点的所有子节点求出并将其放入一个待求子节点的队列。通过遍历这个队列(队列在 遍历过程中不断增长)完成搜索。而DFS的作法则是将每一条合法路径求出后再转而向上求第二条合法路径。而在回溯法中,一般都用DFS。为什么呢?这是因 为可以通过约束函数杀死一些节点从而节省时间,由于DFS是将路径逐一求出的,通过在求路径的过程中杀死节点即可省去求所有子节点所花费的时间。FIFO 理论上也是可以做到这样的,但是通过对比不难发现,DFS在以这种方法解决问题时思路要清晰非常多。
下面以leetcode中Letter Combinations of a Phone Number 来分析。
题目如下:
Given a digit string, return all possible letter combinations that the number could represent.
A mapping of digit to letters (just like on the telephone buttons) is given below.
Input:Digit string "23" Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
Note:
Although the above answer is in lexicographical order, your answer could be in any order you want.
/*
a b c
/ | \ / | \ / | \
/ | \ / | \ / | \
/ | \ / | \ / | \
d e f d e f d e f
*/
然后用DFS来遍历就可以了,这个与回溯不同的是回溯会加上约束条件,如果条件不满足就返回上一层,而本例则没有约束条件。
#include<iostream>
#include <string>
#include <stack>
#include <vector>
using namespace std;
class Solution {
public:
void dfs(vector<string>& re, int cur_dep, string& cur_re, string digits){
static string tel_board[] = {
"0", "1", "abc", "def", "ghi",
"jkl", "mno", "pqrs", "tuv", "wxyz", };
if (cur_dep == digits.length()){//说明已经出现一种组合,添加到结果里
re.push_back(cur_re);
return;
}
string cur_str = tel_board[digits[cur_dep] - '0'];
for (unsigned int i = 0; i < cur_str.length(); ++i)
{
cur_re.push_back(cur_str[i]);
dfs(re, cur_dep + 1, cur_re, digits); //DFS:深度加1
cur_re.pop_back(); //非常关键,用来返回上一层
}
}
vector<string> letterCombinations(string digits) {
vector<string> re;
string cur_re("");
dfs(re, 0, cur_re, digits);
return re;
}
};
int main()
{
Solution s;
vector<string> result;
result= s.letterCombinations("23") ;
for (int i = 0; i < result.size(); i++)
cout << result[i] << " ";
cout << endl;
return 0;
}
时间复杂度:O(n1*n2*……*nk)