递归调用里的性能问题(js)
说明
这是在codewars.com上刷的一道js练习题,在此做个记录
问题描述
The Fibonacci sequence is traditionally used to explain tree recursion.
斐波那契序列通常是用来解释递归调用。
function fibonacci(n) {
if(n==0 || n == 1)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
This algorithm serves welll its educative purpose but it's tremendously inefficient, not only because of recursion, but because we invoke the fibonacci function twice, and the right branch of recursion (i.e. fibonacci(n-2)) recalculates all the Fibonacci numbers already calculated by the left branch (i.e. fibonacci(n-1)).
这个算法以教学为目的,但非常低效的,不仅因为递归,而且两次调用fibonacci函数,在函数里面右侧调用的fibonacci(n-2) 在表达式左侧调用fibonacci(n-1)时就已完全计算过一遍。
This algorithm is so inefficient that the time to calculate any Fibonacci number over 50 is simply too much. You may go for a cup of coffee or go take a nap while you wait for the answer. But if you try it here in Code Wars you will most likely get a code timeout before any answers.
这个算法效率是如此之低,斐波纳契数超过50的实在太多了。你可以去喝杯咖啡或去睡午觉时等待答案。但如果你就用这个代码在codewars上很可能得到一个超时错误。
For this particular Kata we want to implement the memoization solution. This will be cool because it will let us keep using the tree recursion algorithm while still keeping it sufficiently optimized to get an answer very rapidly.
对于这个特定卡塔(类似打怪升级里面的级数),我们想实现缓存的解决方案。这是特别酷的,因为它将让我们继续使用递归算法,同时仍然保持足够迅速的得到一个答案。
The trick of the memoized version is that we will keep a cache data structure (most likely an associative array) where we will store the Fibonacci numbers as we calculate them. When a Fibonacci number is calculated, we first look it up in the cache, if it's not there, we calculate it and put it in the cache, otherwise we returned the cached number.
memoize的版本的诀窍是,保持一个缓存数据结构(最有可能的关联数组),将斐波纳契数列的值缓存。当获取一个斐波那契数列值时,首先在缓存中查找,如果有则直接返回值,如果没有,再计算并把它放进缓存。
Refactor the function into a recursive Fibonacci function that using a memoized data structure avoids the deficiencies of tree recursion Can you make it so the memoization cache is private to this function?
使用memoize的数据结构重构函数的递归Fibonacci以避免递归调用的缺陷。
分析
斐波那契数列里面不断的递归调用自身,列入输入的是 70,那么需要计算69和68的值。
在计算69的过程中又计算了 68、67、、、、、1。 计算 68的过程又计算了 67、66、、、、、、、1的值,如此重复计算的值太多了,花费的时间也就比较多。
缓存思想恰好可以减少不必要的重复计算。当第一遍计算69的值时就递归计算了 68、67、66、、、1的值,之后的每次都先查看是否有缓存,有就直接返回缓存值,避免了重复计算。
代码
let cache = {};
let fibonacci = function(n) {
if(n==0 || n == 1)
return n;
if(cache[n]){
return cache[n];
}
return cache[n] = fibonacci(n-1) + fibonacci(n-2);
}
性能测试
//没有缓存时
let tesetNum = 40;
console.time('NoCache');
function fibonacci1(n) {
if(n==0 || n == 1)
return n;
return fibonacci1(n-1) + fibonacci1(n-2);
}
fibonacci1(tesetNum);
console.timeEnd('NoCache');
// 使用缓存时
console.time("HasCache");
let cache = {};
let fibonacci = function(n) {
if(n==0 || n == 1)
return n;
if(cache[n]){
return cache[n];
}
return cache[n] = fibonacci(n-1) + fibonacci(n-2);
}
fibonacci(tesetNum);
console.timeEnd('HasCache');
// 输出
// NoCache: 1717.834ms
// HasCache: 0.159ms
通过性能测试可以看到,当测试数是40时不适用缓存消耗的时间就是使用缓存的1700多倍(好可怕的数据),我试了下当测试数据是300时,,,,,,,,我就等不急它的执行了。
使用场景
当递归调用里有大量重复计算的情景,或者组件、数据等重复加载的情况下,使用缓存是个不错的选择(典型的以空间换时间)
递归调用里的性能问题(js)的更多相关文章
- Python第七天 函数 函数参数 函数里的变量 函数返回值 多类型传值 函数递归调用 匿名函数 内置函数
Python第七天 函数 函数参数 函数里的变量 函数返回值 多类型传值 函数递归调用 匿名函数 内置函数 目录 Pycharm使用技巧(转载) Python第一天 ...
- js 递归调用
js递归调用 function fact(num) { ) { ; } else { ); } } 以下代码可导致出错: var anotherFact = fact; fact = null; al ...
- 函数直接写在html页面的<script>里可以调用,但是单独放在js文件里不能调用
1.函数直接写在页面相当于是你本页调用,所以理所应当可以调用 2.js单独文件不能调用是因为你没有引用js文件,如果引用了的话,也是可以调用的. 引用方式,你可以直接拖拽(我一般都是拖拽,因为路径准确 ...
- 2016-04-25-信息系统实践手记6-JS调用Flex的性能问题一例
layout: post title: 2016-04-25-信息系统实践手记6-JS调用Flex的性能问题一例 key: 20160425 tags: GIS JS FLEX 技术选型 性能 API ...
- 使用Map辅助拼装树状结构,消除递归调用
目前菜单或其他树状结构在数据库中的存储,多数是以一个parentid作为关联字段,以一维形式存储.使用时全部查询出来,然后在内存中拼装成树状结构.现在主要涉及的是拼装方法的问题. 一般可以进行 递归调 ...
- 你好,C++(27)在一个函数内部调用它自己本身 5.1.5 函数的递归调用
5.1.5 函数的递归调用 在函数调用中,通常我们都是在一个函数中调用另外一个函数,以此来完成其中的某部分功能.例如,我们在main()主函数中调用PowerSum()函数来计算两个数的平方和,而在P ...
- JavaScript函数之实际参数对象(arguments) / callee属性 / caller属性 / 递归调用 / 获取函数名称的方法
函数的作用域:调用对象 JavaScript中函数的主体是在局部作用域中执行的,该作用域不同于全局作用域.这个新的作用域是通过将调用对象添加到作用域链的头部而创建的(没怎么理解这句话,有理解的亲可以留 ...
- Java中的递归调用
Java中不合理的使用递归调用,可能会导致栈内存溢出,这点是需要注意的. java将为每个线程维护一个栈,栈里将为每个方法保存一个栈帧,栈帧代表了一个方法的运行状态. 也就是我们常说的方法栈.最后一个 ...
- Java中函数的递归调用
说到递归,java中的递归和C语言中也是很相似的,在Java中,递归其实就是利用了栈的先进后出的机制来描述的. public class HelloWorld { public static void ...
随机推荐
- Python基础之字符编码
前言 字符编码非常容易出问题,我们要牢记几句话: 1.用什么编码保存的,就要用什么编码打开 2.程序的执行,是先将文件读入内存中 3.unicode是父编码,只能encode解码成其他编码格式 utf ...
- @Component @Controller @Service @Repository@Resourse
@Component @Controller @Service @Repository@Resourse这些全部是Spring提供的注解. 其中@Component用来表示把一个类纳入spring容器 ...
- Spring事件解析
首先介绍Spring事件相关类的关系: 其中EventListener与EventObject均是Java SE的范畴,源码如下: package java.util; public interfac ...
- form表单提交图片禁止跳转
问题: 最近在做项目时,遇到上传图片需求,且在不跳转的情况下获取到返回信息 思路: 1. 使用ajax发送异步请求,经多次测试,最终以失败告终 2. iframe 禁止跳转(未尝试) 3. 修改fo ...
- 【TOP】top命令的load average的意义
一. 怎么查看机器负载 uptime 命令: $uptime 14:32:32 up 108 days, 23:04, 17 users, load average: 0.06, 0.08, 0.0 ...
- Spring-mybatis没有了XXXmapper.java和Dao的实现类还有Service的实现类
对于刚学过框架的同学可能知道,mybatis有两种主要的配置文件: SqlMapConfig.xml(mybatis全局配置文件,名称不固定,用来配置运行环境(数据源.事务) XXXmapper.xm ...
- 【机器学习】反向传播算法 BP
知识回顾 1:首先引入一些便于稍后讨论的新标记方法: 假设神经网络的训练样本有m个,每个包含一组输入x和一组输出信号y,L表示神经网络的层数,S表示每层输入的神经元的个数,SL代表最后一层中处理的单元 ...
- css鼠标样式cursor
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- nodeJS之URL
前面的话 在HTTP部分,详细介绍了URL的相关知识.而nodejs中的url模块提供了一些实用函数,用于URL处理与解析.本文将详细介绍nodeJS中的URL URL对象 解析 URL 对象有以下内 ...
- 用Django做一个省份选择器
做一个省份选择器 使用django做后端, mysql数据库, jQuery 列出结构主要的文件, 其它配置比较简单 models.py 因为所有数据的结构基本一致, 把所有省份, 市和区全部存储一张 ...