回溯法

参考 - 剑指Offer

回溯法可以看成蛮力法的升级版,它从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案.

回溯法解决的问题的特性:

  • 可以形象地用树状结构表示:

    • 节点: 算法中的每一个步骤
    • 节点之间的连接线: 每个步骤中的选项,通过每一天连接线,可以到达下一个子步骤
    • 叶子节点: 代表一个步骤的最终状态
  • 如果在叶节点的状态满足需求,那么我们找到了一个可行的解决方案

  • 如果在叶节点的状态不满足约束条件,那么只好回溯到它的上一个节点再尝试其他的选项。如果上一个节点所有可能的选项都已经试过,并且不能达到满足约束条件的终结状态,则再次回溯到上一个节点.如果所有节点的所有选项都已经尝试过仍然不能达到满足约束条件的终结状态,该问题无解

栗子 - 数组总和

题目参考 - 39.数组总和

算法思路:

  • 变量:

    • 使用len缓存当前数组的长度
    • 使用path缓存当前的路径
    • 使用res缓存要返回的结果
  • 处理:
    • 为了方便后续的剪枝操作,需先对数组进行排序
  • 使用深度优先算法,传入3个参数: resides(离目标还差多少), path, begin(从哪一个开始添加)
    • 每次进入时,判断一下,resides是否小于0:

      • 是: return
      • 否: 不做处理
    • 之后判断resides是否等于0
      • 是: 证明找到一个条符合要求的路径,将path推入res中(此处需特别注意,数组是JS中的引用类型.在后文中会回溯,最终的path是一个空数组. 如果直接将path推入res.其实是将path的内存地址推入res.最终会根据地址寻找到空数组.因此此处推入的是path.slice()). slice方法参考
      • 否: 不做处理
    • 到这里,循环遍历candidates数组
      • 每次将当前的值推入路径
      • 然后调用dfs函数
      • dfs函数结束之后,要进行回溯操作,即将对path使用pop()方法
var combinationSum = function(candidates, target){let len = candidates.length,path = [],res = []candidates.sort((a,b)=>a-b)function dfs(resides, path, begin){if(resides < 0) returnif(resides == 0) return res.push(path.slice())  // 此处需要返回一个新的数组,不能使用同一个内存中的数组for(let i = begin; i < len ; i++){if(resides - candidates[i] < 0) break;path.push(candidates[i])dfs(resides - candidates[i], path, i)path.pop()}}dfs(target, path, 0)return res
}

注意: res.push(path)时,由于path是个引用类型.因此实际上push进的是一个十六进制地址.可以看做下面:

res[0] = ‘0xffffffff’

当后面回溯path.pop()时, 内存0xffffffff中的值会改变.

最后一次回溯 内存0xffffffff中的值为 []

因此 res[0] = []

而我们需要得到的是res[0] = [a,b,c] 这样的结构。 因此我们每次在push时,需要重新生成一个数组传入,即使用path.slice()

栗子 - 数组总和II

题目描述

算法思路:

以传入参数combinationSum2([10, 1, 2, 7, 6, 1, 5], 8)为栗子进行说明:先将传入的数组candidates进行升序排列, 即candidates = [1,1,2,5,6,7,10], 采用树的深度优先遍历.

  • resides: 离target =8 , 差多少
  • candicates: 当前的用于操作的候选数组
  • path: 当前的路径
  • res: 最终返回的结构

当resides < 0时, 直接退出当前

当resides ===0 时,代表 path中的数组满足条件. 将path推入res中 res.push(path.slice())

当resides > 0 时, 遍历candidates数组:

  • 每次判断 resides - candidates[i] 是否大于0 , 若小于0则进行剪枝(退出当前循环)

  • 同时要考虑[1,2, 5] 和 [1,7]的情况.因为原数组中有2个1, 只需第一个1即可. if(candidate[i] !== candidate[i-1])

  • 到这里就是正常的递归回溯工作了:

    • 每次将 candidates[i]推入path中.
    • 然后调用 dfs()递归
    • 出来回溯. path.pop()
var combinationSum2 = function(candidates, target) {candidates.sort((a, b) => a - b)var res = []function dfs(resides, candidates, path) {if (resides < 0) returnif (resides === 0) res.push(path.slice())for (let i = 0, len = candidates.length; i < len; i++) {if (candidates[i] !== candidates[i - 1]) {if (resides - candidates[i] < 0) breakpath.push(candidates[i])dfs(resides - candidates[i], candidates.slice(i + 1), path)path.pop()}}}dfs(target, candidates, [])return res
}

栗子 - 组合总和 III

题目参考

算法思路: 还是使用深度优先.

  • candidates:当前用于操作的数组
  • path: 当前的路径
  • resides: 当前距离目标的差值

每次进入 dfs:

  • 首先检查条件是否满足:

    • resides若为负数,退出当前环境
    • path.length 若等于 k 则退出当前环境
    • candidates存在且candidates的长度为0,则退出当前环境
  • 然后循环candidates,对于每个candidates[i]

    • 得到当前的path = […path, candidates[i]]
    • 计算当前path长度,若等于k 则判断 resides - candidates[i] 是否为0, 若为0,则将当前路径推入res中。并退出
    • 递归调用 dfs(resides - candidates[i], candidates.slice(i+1), path)
    • 这里需要回溯 path.pop()
var combinationSum3 = function (k, n) {var res = []function dfs(candidates, resides, path) {if ((candidates && candidates.length == 0) || path.length > k || resides < 0) returnfor (let i = 0, len = candidates.length; i < len; i++) {path = [...path, candidates[i]]if (path.length === k && resides - candidates[i] == 0) return res.push(path.slice())dfs(candidates.slice(i + 1), resides - candidates[i], path)path.pop()}}dfs([1, 2, 3, 4, 5, 6, 7, 8, 9], n, [])return res
}

栗子 - 从根到叶子节点数字之和

题目参考

思路:

  • 使用 sum 保存总和, r 保存当前树结构的根节点, path 保存当前的路径(数组类型)
  • 使用dfs深度优先遍历树, 对于每次遍历 dfs(root, path)
    • 判断r 是否为空,若空则返回,否则执行下一步
    • 更新当前的path = […path, r.val]
    • 判断当前是否为叶子节点:
      • 若是则: sum += path.join('') - 0. 其中-0是数字的隐式类型转换
    • dfs(r.left, path)
    • dfs(r.rigth, path)
    • 回溯 path.pop()
var sumNumbers = function (root) {var sum = 0;function dfs(r, val) {if (!r) returnval = [...val, r.val]if (!r.left && !r.right) {return sum += val.join('') - 0}dfs(r.left, val)dfs(r.right, val)val.pop()}dfs(root, '')return sum
};

栗子 - 路径总和II

题目参考

算法思路:大体的思路是深度优先遍历,遍历顺序5 -> 4 -> 11 -> 7 --> 11 -> 2 --> 11 --> 4 -->5

其中,->代表下一个-->代表回退。递归循环调用dfs函数.传入当前的树结构的根节点r、距离总和差值resides和当前的路径path

每次dfs循环如下:

  • 判断r是否为null, 若是则返回
  • 生成当前的path.
  • 判断 resides - r.val 是否为0
    • 若为0,则判断当前是否是叶子节点
  • dfs 当前节点的左节点和右节点
var pathSum = function(root, sum){var res = []function dfs(resides, r, path){if(!r) returnpath = [...path, r.val]     // 这里使用[]隐式规则,在新的内存空间中生成了一个数组if(resides - r.val === 0 && !r.left && !r.right) return res.push(path)dfs(resides - r.val, r.left, path)dfs(resides - r.val, r.right, path)}dfs(sum, root, [])return res
}

算法 --- 回溯法相关推荐

  1. 基本算法-回溯法(迷宫问题)

    作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 前言 本文介绍一种经典算法--回溯法,可作为迷宫问题的一种解法,以下是本篇文章正文内容,包括算法 ...

  2. java背包算法回溯法_【算法分析】实验 4. 回溯法求解0-1背包等问题

    [TOC] 实验内容 本实验要求基于算法设计与分析的一般过程(即待求解问题的描述.算法设计.算法描述.算法正确性证明.算法分析.算法实现与测试),通过回溯法的在实际问题求解实践中,加深理解其基本原理和 ...

  3. 算法---回溯法--模板解法

    回溯法问题:实际上就是一个决策树的遍历过程 分为三步: 路径:已做出选择的路径. 选择列表:当前可以做的选择 结束条件:就是到达决策树的底层,无法再做出选择的条件.(退出条件 ) template&l ...

  4. 回溯法解决tsp问题 matlab,回溯法求解tsp问题

    回溯法以这种工作方式递归地在解空间中搜索, 直至找到所 要求的解或解 空间中已无活结点时为止. 回溯法求解 TSP 问题,首先把所有的顶点的访问标志初始化为 0,...... 回溯法求解 TSP 问题 ...

  5. 算法分析与设计——回溯法实验报告

       算法导论  课程设计 成 绩 题    目:    回 溯 法 学院班级:        1613013         学    号:      16130130216       姓     ...

  6. 算法设计与分析第5章 回溯法(二)【回溯法应用】

    第5章 回溯法 5.2 应用范例 1.0-1背包问题 有n件物品和一个容量为c的背包.第i件物品的重量是w[i],价值是p[i].求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和 ...

  7. 算法设计与分析第5章 回溯法(一)【回溯法】

    第5章 回溯法 5.1 回溯法 1.回溯法的提出  有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法. 2. 问题的解空间 (1)问题的解向量:回溯法希望 ...

  8. python回溯算法_什么是回溯法,Python解法交流?

    只有去多做题,才能慢慢掌握.力扣​leetcode-cn.com LeetCode 上的解释 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 ...

  9. 五大经典算法之回溯法

    一.基本概念   回溯法,又称为试探法,按选优条件向前不断搜索,以达到目标.但是当探索到某一步时,如果发现原先选择并不优或达不到目标,就会退回一步重新选择,这种达不到目的就退回再走的算法称为回溯法. ...

最新文章

  1. 全球安全行业融资收购简报(2016年2月)
  2. java 深拷贝 流_(转)Java技巧:深拷贝的两种方式
  3. Unity 中的协同程序
  4. 好好学python·集合
  5. 各种编程语言的深度学习库整理(中英版)
  6. 挂载WebDav提供的网络存储----Client端
  7. return两个返回值_异步函数的两个视角
  8. unity android sd卡路径,一、文件与路径——2、unity路径特点写法和文件读写全攻略...
  9. tcmalloc编译
  10. 《互联网运营智慧》随书视频
  11. Webform(分页、组合查询)
  12. 用python计算100以内所有奇数的和_Python-while 计算100以内奇数和的方法
  13. 它不是哆啦A梦 也能满足你的挑剔需求
  14. FFmpeg 以及帧率的解释
  15. STM32f407程序移植到GD32F407
  16. JDBC报错:The server time zone value is unrecognized or represents more than one time zone 已解决
  17. 蚂蚁花呗账单分期和交易分期的费用如何计算?
  18. 光纤布线兵法之热点问题篇(二)
  19. 为什么有些人说单片机简单,我学起来这么吃力?
  20. 智慧灌区、水利、闸门控制、智慧监控、水质监测、气象站、水质站、灌区监测、渠道监测、水情监测、降水监测、渠道水位、流量、水量、干渠、支渠、枢纽闸门、预警管理、工程管理、任务管理、系统管理、axure原型

热门文章

  1. wp自定义帖子没标签_拼多多搜索智能推广和自定义推广区别在哪里?
  2. source insight 函数不能跳到definition_小技能: Windows10突然不能复制粘贴谁搞鬼
  3. java s1_转!!Java 基础面试题的剖析: short s1=1;s1 = s1 +1 报错? s1+=1 呢
  4. GPU Pro2 - 3.Procedural Content Generation on the GPU
  5. php验证码背景图是数字,ThinkPHP5.0.20验证码背景图片
  6. [FY20 创新人才班 ASE] 第 1 次作业成绩
  7. 启动Cognos时报0106错误
  8. 【openssl】利用openssl完成X509证书和PFX证书之间的互转
  9. 《推荐系统实践》样章:如何利用用户标签数据
  10. css3 卡片hover3D效果