递归调用里的性能问题(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 ...
随机推荐
- 修改tomcat编码方式
打开server.xml <Connector URIEncoding="UTF-8" connectionTimeout="20000" port=&q ...
- 51nod_1714:B君的游戏(博弈 sg打表)
题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1714 nim游戏的一个变形,需要打出sg函数的表 #incl ...
- AI人工智能-Python实现人机对话
[前言] AI 在人工智能进展的如火如荼的今天,我们如果不尝试去接触新鲜事物,马上就要被世界淘汰啦~ 本文拟使用Python开发语言实现类似于WIndows平台的“小娜”,或者是IOS下的“Siri” ...
- 在CentOS7上部署OpenStack 步骤详解
OpenStack作为一个由NASA(美国国家航空航天局)和Rackspace合作研发并发起的,开放源代码项目的云计算管理平台项目.具体知识我会在后面文章中做出介绍,本章主要按步骤给大家演示在Cent ...
- 程序员面试必备-链表各种操作及其实现方法(c实现)
链表是一种最简单的数据结构之一,经常会被面试官用来考察应聘者的基础扎不扎实,最近也到了求职季,所以我把自己对链表的一些理解写出来,希望能跟大家交流交流: 链表的概念其实挺简单,无非就是一个利用指针将数 ...
- Node.js 入门:Express + Mongoose 基础使用
前言 Express 是基于 Node.js 平台的 web 应用开发框架,在学习了 Node.js 的基础知识后,可以使用 Express 框架来搭建一个 web 应用,实现对数据库的增删查改. 数 ...
- JAVA二维数组的复制
JAVA二维数组的复制 笔者今天做一道ccf题目时,遇到要将二维数组拷贝复制时,没有用常规的那种一个一个数的复制,用的是System.arraycopy()来进行复制,下面介绍这个函数的一些注意点: ...
- 异常解决:Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
异常描述 这个异常通常有如下信息: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failu ...
- 【css】主要的块状元素(block element)和内联元素(inline element行内元素)
内联元素:只在行内发生作用,设置宽高不起作用,不会影响文字内容,使其换行等.竖直方向和间距也不起作用 display可以强制转换行内元素和块状元素,还可以取消显示none 块元素(bloc ...
- Nginx配置抵御DDOS或CC攻击
防攻击的思路我们都明白,比如限制IP啊,过滤攻击字符串啊,识别攻击指纹啦.可是要如何去实现它呢?用守护脚本吗?用PHP在外面包一层过滤?还是直接加防火墙吗?这些都是防御手段.不过本文将要介绍的是直接通 ...