回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原选择不优或达不到目标,就退一步重新选择,这种走不通就退回再走的技术为回溯法。
在我们求解某些问题时,常常会把答案一一枚举(列举),然後再检查答案的是否正确。但一一枚举会非常耗时,而且常常枚举出来的数量远多于所需要的答案。原因在于在枚举进行到一半的时候,如果该解决问题的路径是错的,还是会不断的继续向下枚举。但其实我们可以在发现这条路径不可能找出答案的时候,就不再往下枚举了,这种思想就是回溯(Backtracking)算法。
回溯法算法基本思想
回溯算法实际上是一个类似枚举的搜索尝试过程,是在搜索尝试过程中寻找问题的解。当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原选择不优或达不到目标,就退一步重新选择,这种走不通就退回再走的技术为回溯法。而满足回溯条件的某个状态的点称为“回溯点”。
1、回溯法的适用:
许多问题,当需要找出它的解集(全部解)或要求回答什么解是满足某些约束条件的最优解时,往往要使用回溯法。
2、有组织的穷举式搜索:
回溯法的基本做法是搜索或者有的组织穷尽搜索。避免搜索所有的可能性。即避免不必要的搜索。这种方法适用于解一些组合数相当大的问题。
3、搜索解空间树:
回溯法在问题的解空间树中,按深度优先策略(DFS),从根结点出发搜索解空间树。
算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。
如果肯定不包含(剪枝过程),则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
为了实现回溯,我们先弄明白以下两个问题:
1)首先应该明确问题的解空间。
2)其次是组织解空间以便它能用以被搜索到。
回溯法解题的一般步骤
- 针对所给问题,确定问题的解空间:首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
- 确定结点的扩展搜索规则
- 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
递归回溯
迭代回溯
回溯法的应用举例:
排列问题:
举个例子来说:如果对"ABCD"四个字母进行排列,我们知道有24(4!)种答案,如果枚举所有可能的答案,就会产生生AAAA,AAAB,...AADD這種不符合的答案,而且总共会产生64(4^4)种結果,比24大了不少。
程序代码如下:
#include
#include
using namespace std;
string str;
string Ans; //保存答案
bool choosed[100] = {0}; // choosed[i]==1表示编号i已經出現過
void backtracking(const int &N);
int main()
{
str = "ABCD";
backtracking(4);
}
void backtracking(const int &N) // N表示有N个字母
{
if (Ans.size() == N) { // 终止条件检查
cout << Ans << endl;
return;
}
for (int i = 0; i < N; ++i) {
if (choosed[i] == 0){ // 如果這个编号还没出现过 Ans.push_back(str[i]); // 将它放入Ans
choosed[i] = 1; // 並且让choosed[i]变成1,避免之后选到
backtracking(N);
Ans.pop_back();
choosed[i] = 0;
}
}
}
可以发现回溯算法在每次遍历前都会先检查答案的可行性,必须确认“到目前为止都是正确的”才会继续向下,因此就能避免产生错误的答案,所以上面的例子就少了40个错误的答案。
许多复杂的,规模较大的问题都可用回溯法,回溯法有“通用解题方法”的美称。
应用回溯法解决的一些经典问题:
1)装载问题
2)批处理作业调度
3)符号三角形问题
4)n后问题
5)0-1背包问题
6)最大团问题
7)图的m着色问题
8)旅行售货员问题
9)圆排列问题
10)电路板排列问题
11)连续邮资问题