一、重叠子问题

斐波那契数列没有求最值的问题,因此严格来说它不是最优解问题,当然也就不是动态规划问题。但它能帮助你理解什么是重叠子问题。首先,它的数学形式即递归表达是这样的:

def Fibonacci(n):if (n == 0 or n == 1):return nif (n > 1):return Fibonacci(n - 1) + Fibonacci(n - 2)def main():result = Fibonacci(4)print(result)if __name__ == "__main__":main()

这个代码本身没有问题, 但是它的效率极低。假设上面的函数调用输入是10,把递归树画出来:

如果要计算原问题F(10), 就需要先计算出问题F(9)和F(8), 如果要计算F(9), 就需要先计算出子问题F(8) 和 F(7),以此类推。这个递归的终止条件是当F(1)=1 或 F(0)=0时结束。

看完斐波那契数列的求解树之后,就会发现问题:

  • 用红色标出的两个区域中,它们的递归计算过程完全相同

这意味着,第2个红色区域的计算是 “完全没有必要的”,它是重复的计算。因为我们已经在求解F(7)的时候把F(6)的所有情况算过了。因此我们把第2个红色区域的计算称为重叠子问题

二、使用备忘录解决重复问题

既然存在重复的子问题,那我们在遇到这些重复的子问题时,只需要执行一次即可,即消灭重复计算的过程。

我们可以创建一个备忘录(memorization),在每次计算出某个子问题的答案后,将这个临时的中间结果记录到备忘录里,然后再返回。

接着,每当遇到一个子问题时,我们不是按照原有的思路开始对子问题进行递归求解,而是先去这个备忘录中查询一下。如果发现之前已经解决过这个子问题了,那么就直接把答案取出来复用,没有必要再递归下去耗时的计算了。

对于备忘录,可以考虑使用以下两种数据结构:

  • 数组(Array),通常对于简单的问题来说,使用一维数组就足够了。
  • 哈希表(Hash table),如果你存储的状态不能直接通过索引找到需要的值(比如斐波那契数列问题,你可以直接通过数组的索引确定其对应子问题的解是否存在,如果存在你就拿出来直接使用),比如你使用了更高级的数据结构而非简单的数字索引,那么你还可以考虑使用哈希表,即字典来存储中间状态,来避免重复计算的问题。

下面看看用数组实现的备忘录来解决斐波那契数列的代码:

def Fibonacci(n, memo):if (n == 0 or n == 1):return n# 如果备忘录中找到了之前计算的结果,那就直接返回,避免重复计算if (memo[n] != None):return memo[n]if (n > 1):memo[n] = Fibonacci(n - 1, memo) + Fibonacci(n - 2, memo)return memo[n]# 如果数值无效(比如 < 0),则返回0return 0def FibonacciAdvance(n):memo = [None] * (n + 1)return Fibonacci(n, memo)def main():result = FibonacciAdvance(4)print(result)if __name__ == "__main__":main()

实际上,这就是我们所熟知的“剪枝与优化”,把一棵存在巨量冗余的递归树通过剪枝,改造成了一幅不存在冗余的递归图,极大减少了子问题(即递归图中节点)的个数。通过这种方式,我们大幅缩减了算法的计算量,因为所有重复的部分都被跳过了

三、重叠子问题的限制

有些问题虽然看起来像包含“重叠子问题”的子问题,但是这类子问题可能具有后效性,但我们追求的是无后效性。
所谓无后效性,指的是在通过 A 阶段的子问题推导 B 阶段的子问题的时候,我们不需要回过头去再根据 B 阶段的子问题重新推导 A 阶段的子问题,即子问题之间的依赖是单向性的。
换句话说,如果一个问题可以通过重叠子问题缓存进行优化,那么它肯定都能被画成一棵树。

四、方法的弊端

通过重叠子问题缓存可以极大加速我们的代码执行效率。但是凡事都有两面性,毋庸置疑,这种方案肯定是通过某种牺牲换取了性能的提升。
在硬币找零问题中,我们就可以利用备忘录来避免重复计算。但这样有个问题,如果我们的钱币总额数量非常巨大,那这个数组的大小就会非常巨大,导致的结果就是会占据大量的内存存储空间,而且有很多的数字其实是不会被求解的,存在很多的“存储空洞”。显然,这是一种浪费。
所以在解题的过程中,你需要根据实际情况,在空间和时间中寻求一个平衡,将这个问题考虑在内。

五、总结与升华

备忘录的思想极为重要,特别是当求解的问题包含重叠子问题时,只要问题包含重复计算,你就应该考虑使用备忘录来对算法时间复杂度进行简化。具体来说,备忘录解法可以归纳为:
1.用数组或哈希表来缓存已解的子问题答案,并使用自顶向下的递归顺序递归数据;
2.基于递归实现,与暴力递归的区别在于备忘录为每个求解过的子问题建立了备忘录(缓存);
3.为每个子问题的初始记录存入一个特殊的值,表示该子问题尚未求解;
4.在求解过程中,从备忘录中查询。如果未找到或是特殊值,表示未求解;否则取出该子问题的答案,直接返回。

解决递归中的重复计算问题相关推荐

  1. 解决Mysql中删除重复记录的问题

    题记 本文主要介绍Mysql数据库表中,如何删除相同索引值.更新日期较早的记录行. 1.相同记录的由来 以Mysql为例,根据表的设计原则,表中不可能存在两条完全相同的记录. 第一范式(1NF):字段 ...

  2. 解决项目中遇到重复引用依赖冲突问题

    比方说大家遇到类似: The application could not be installed: INSTALL_FAILED_CONFLICTING_PROVIDER Can't install ...

  3. php 计算字符串相邻最大重复数_php如何解决字符串中重复字符的次数并且排序输出的方法...

    在php开发中有这样的需求.在指定的字符串中提取出每个单位字符出现的次数,并且倒序排序,截取前4个.留作使用.刚拿到这个需求的时候,我想了想,难道要把每个字符全部切割出来之后,一一的比对计算出相应的出 ...

  4. js浮点数精度丢失问题及如何解决js中浮点数计算不精准

    js浮点数精度丢失问题及如何解决js中浮点数计算不精准 参考文章: (1)js浮点数精度丢失问题及如何解决js中浮点数计算不精准 (2)https://www.cnblogs.com/ranyonsu ...

  5. rf中resourceid_解决VC++ MFC程序resource.h头文件中ID重复问题

    解决VC++ MFC程序resource.h头文件中ID重复问题 2018-12-07 一般MFC开发的时候,如果有些资源是从其他工程中移植到本工程的, 而在资源移植的时候都要将对应的资源ID复制到本 ...

  6. html中文本重复,在网页中去除文本列表中重复行与计算重复次数的代码原理

    在我们使用记事本等文本工具整理查看段落列表数据时,经常会遇到多行出现重复的相同内容,当段落行数较多时,如果手动去一行行的找重复的行,会比较麻烦,那么我们该怎样去实现简单的去除文本组成的列表中的重复行以 ...

  7. 数组x中数据复制到数组y中,重复的数据只存储一次,最后输出y;计算x中数据的平均值ave及大于平均值的元素个数n。c++实现

    题目描述 编程序,实现如下功能: (1)定义两个一维数组x,y,不超过50个元素. (2)从键盘输入k个整数到数组x中. (3)计算x中数据的平均值ave及大于平均值的元素个数n并输出. (4)将数组 ...

  8. 剑指offer_31:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但

    题目:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决.但是,如果向量中包 ...

  9. C# 解决{System.Data.OleDb.OleDbException (0x80004005): 提示由于将在索引,主关键字或关系中创建重复的值,请求对表的改变没有成功

    今天解决下 {System.Data.OleDb.OleDbException (0x80004005): 提示由于将在索引,主关键字或关系中创建重复的值,请求对表的改变没有成功. 最近在用C#将文件 ...

最新文章

  1. python邮件发送哪个好_最全总结!聊聊 Python 发送邮件的几种方式
  2. excel2007-分页显示透视表
  3. 图片竖轮播html,JS实现纵向轮播图(初级版)
  4. java对数据库的操作_java对数据库的操作(jdbc)
  5. RHEL4-VNC服务(二)vnc服务器的配置
  6. linux中用户信息存储在,在linux中,用于存放用户信息的两个文件是/etc/passwd和/etc/shadow。()...
  7. 在职研究生-学术硕士和专业硕士有什么区别?
  8. c语言 char作用,c语言中char型数据能直接运算吗?
  9. Excel表格模板打包下载┆收集了各类各行业Excel表格、word模板
  10. Java枚举的打印_如何在java中打印所有枚举值?
  11. java版林地府邸种子_我的世界林地府邸地图种子代码分享
  12. ubuntu 16.04 开机开启小键盘数字键
  13. WinSCP连接被拒绝
  14. The connection to adb is down, and a severe error has occured.
  15. 计算机汉字的输入和编辑教案,微机教案:汉字输入法
  16. 苹果cms10的一次尝试发现了苹果cms10被挂马极有可能是苹果cms作者故意的js漏洞或后门导致
  17. 名帖337 张旭 草书《古诗四帖》
  18. 爬虫(利用正则表达式爬取百度新闻(淘宝))
  19. 作为技术团队TL,如何运用OKR提高团队产出
  20. Robbery UVa-707

热门文章

  1. Leetcode - 347. Top K Frequent Elements(堆排序)
  2. 未排序正整数组中累加和为指定值的最长子数组长度
  3. 强化学习笔记 experience replay 经验回放
  4. tableau实战系列(二十五)-如何将 R语言与tableau进行结合,实现聚类分析
  5. php 错误关闭_五种方法教你如何关闭php错误回显信息
  6. JVM 生态系统 2018 调查报道
  7. Flink JAR包上传和运行逻辑
  8. 面向程序员的数据挖掘指南: 第二章 从推荐系统开始
  9. Java7里try-with-resources分析--转
  10. Let's do our own full blown HTTP server with Netty--转载