一.相关概念:
回溯法:定义在解空间树上搜索解的算法,具有剪枝函数的深度优先生成树
问题的解向量:一个问题的解能表示成一个n元组(x1,x2...xn),xi取0或1
显约束:对分量xi取值限定
隐约束:对满足问题的解,而对不同分支施加约束
解空间:满足显式约束条件的所有n元组的向量,构成该实例的一个解空间,组织成一颗树即解空间树.
注:同一问题可能有不同的空间树表示,表示越简单,解空间越小,搜索越快
扩展结点:一个正在产生儿子的结点
活结点:一个自身己生成,但其儿子还没有全部生成的结点
死结点:一个所有儿子已经产生的结点
在搜索过程中动态产生问题的解空间树,边搜索边扩展分支
注:数据结构中树深度优先遍历是先创建树,再深度优先通历。而回溯法在任何时刻只保留从根到当前扩展结点的路径,用一个数组X[]存储路径信息,所需空间复杂度O(h(n)),h(n)为叶结点路径长度
二.回溯法解题步骤:
1.针对问题描述,定义解空间
2.确定解空间结构
两类典型解空间树:①子集树/n叉树(左右分支性质不同,eg:0、1代表装或不装,因此剪枝函数适用于每个分支)
void backtrack(int t)
{
if(t>n) output(x);
else
for(int i=0;i<=1;i++)//i分支取值0、1两种情况
{
x[t]=i;
if(constraint(t)&&bound(t) backtrack(t+1));
}
}
②排列树(每个分支)
void backtrack(int t)
if(t>n) output(x);
else
for(int i=t;i<=n:i++)//i分支取值n种情况
{
swap(x[t],x[i]);//展开分支
if(constraint(t)&&bound(t) backtrack(t+1));
swap(x[i],x[t]);//返回过程
}
}
3.以深度优先方式搜索解空间,搜索过程用剪枝函数避免无效搜索 <<避免无效搜索策略两类剪枝函数:①条件约束在扩展结点(左子树)剪去不满足约束条件子树②限界函数(在右子树)剪去得不到最优解的分支>>
三.回溯法计算时间复杂度
(回溯法时间复杂度计算量往往非常高,只有理论意义,没有实际意义):
①计算出解空间树中除叶子层之外的结点总数f(n)
②每个非叶子节点扩展出其下层的所有分支的时间复杂度g(n)
③算法时间复杂度f(n)*g(n)
四.回溯法经典例题
五.回溯法总结
1)回溯法解决的问题,其解一定可以表示成n元组的形式。
(2)确定易于求解的解空间树。常用:子集树、排列树、n叉树等
一个问题有可能对应二种或多种解空间树,评价的标准:最后一层叶子结点的数量越少越好。
(3)剪枝函数的设计:要求在保证剪枝效率的情况下计算尽量简单。
对于子集树,通常左分支使用约束函数、右分支使用限界函数剪枝。对于排列树或n叉树,这两个函数同时应用于每个分支。
(4)回溯法是有套路的,一般在递归调用backtrack函数前、后的代码动作是相反的。
(5)到达叶子结点时,如果是求最优解,你要确定需不需要跟之前的最优解比较来更新最优解。有些情况是必须要经过比较确认后才能更新为当前最优解。