概念
回溯本质上也属于一种暴力枚举,但它比纯暴力更高效,因为它会剪枝,即提前终止不可能的路径。
专门用于枚举所有可能的组合(或排列、子集)。
局限性:时间复杂度为2n,大规模的问题则需要使用动态规划、贪心算法。
每次循环都是一次选择,主体使用的是递归的方式,自己调用自己,递归以后全部结果覆盖所有可能,维护成最终最合适的结果,它能够覆盖全部组合,但通常会剪枝,这个操作生效在在当前路径继续深入将不再有更优解的情况,避免无效递归。
回溯的三要素
回溯的三个要素可以归纳为循环、递归、剪枝,但这不是一个硬性规定,根据实情来使用
循环:用于遍历当前路径所有选择。
递归:深入探索一个允许的选择。
剪枝:后续将无满足可能,提前终止。
回溯的概念图
以下是回溯的概念图,只包含循环和递归,
是我对回溯的一种原始的演示,剪枝是灵活添加的做法。
更加细节地注释:
整体大概的路径轨迹会是这样一个图样:
总之:这呈现了回溯算法的实现结构,可以直观看出它覆盖了所有可能的组合。
【另外】如果需求是 排列(顺序不同则异)而非组合(不看顺序),则通过添加一些逻辑也可以实现。
通过前面的图例,大家应该可以感受到,回溯算法能够实现枚举覆盖所有可能的组合 。
回溯算法的结构
那么,回溯算法的代码怎么写?其实很清晰
声明一个backtrack回溯函数,其内部逻辑如下:
1.首先是剪枝逻辑,满足条件则剪枝,即return,不再执行后续路径的生成,
2.路径选择逻辑,通过循环来实现,每次循环都是基于本路径进行新分支(通常称选择),选择方式为:嵌套调用函数自身,传入i+1、当前累积数据。
【解释】:
i+1:下次将从当前索引的后一个开始选择;
当前累计数据:当前路径的值。
function xxXxxx(){
// ...
function backtrack(start,currentSum){
// 剪枝逻辑
if( 条件符合 ){
// 进行某些更新
return; // 剪枝
}
// 选择逻辑
for(let i = start ; i < len ; i ++){
backtrack(i+1,currenttSum); // 下次选择从当前项的后一项开始
}
}
// ...
}
那么,回溯为什么叫回溯呢?
回溯 = 试错 + 回退 + 剪枝,名字来源于"走不通就退回去,换条路再走"
回溯算法的核心是:
- 深度优先搜索(DFS) :通常沿着一条路径深入到底,再回退。
回溯函数内部的执行顺序
这里需要提出来:如果for()循环中的代码是同步的,则会等待代码执行完毕再进入下一个循环,所以回溯算法实际上就是一条路先走到底,走完了再看另一条路,这样形成的,当然,符合我们直观理解的,则是把整个图拿出来,看每一级调用,代表n个元素的组合这样,但其实际的执行顺序我们有必要清楚。
也就是,回溯算法首先形成一完整的条路,再形成下一条完整的路,每条路从根到任意节点为一个组合,所有路形成后,覆盖所有可能的组合。
- 递归 + 状态撤销:通过递归实现前进,通过撤销选择实现回退。
就拿京东秋招的这道题来讲:最低金额使用代金券 刚下班,领了京东秒购的麦某劳优惠券,想去买汉堡,优惠券最低使用金额是X元,以不点重复的汉堡为前提,我想要以最低的价格使用到优惠券,会提供两行数据,第一行是优惠券最低使用金额X,第二行是一个数组,里面的每个值都表示汉堡的某个价格,请返回最低需要花费多少能够使用到优惠券。
这是一个背包问题的变种,可以使用动归+贪心,也可以使用回溯(较基础)。
function minCostToUseCoupon(X,prices){
prices.sort((a,b)=>{ return a - b; });
let minCost = Infinity;
// 回溯函数
function backtrack(start,currentSum){
// 剪枝逻辑
if(currentSum >= X){
minCost = Math.min(minCost,currentSum);
return;
}
// 选择逻辑
for(let i = start ; i < prices.length ; i++){
backtrack(i+1,currentSum + prices[i]);
}
}
backtrack(0,0); // 从0号索引开始,初始和为0,调用回溯函数
// 如果没有符合条件的组合则返回-1,有则返回minCost
return minCost === Infinty ? -1 : minCost;
}
这里最后一行通过数据是否被改变判断是否有符合的组合,也是初学时比较常见的做法。
那么到这里,从概念——>结构——>直观理解——>回溯的调用执行路径(初学者小细节须知:for循环会等待同步代码再进入下一个循环——>大厂笔试真题练习,可谓是良苦用心,直观又带练。
如果觉得我本文讲得还不错,可以给个小小的赞嘛~~~ 3Q!我是LC_Happy,祝我们都会拥有一个向往的快乐生活!