提升JavaScript递归效率:Memoization技术详解

递归是拖慢脚本运行速度的大敌之一。太多的递归会让浏览器变得越来越慢直到死掉或者莫名其妙的突然自动退出,所以我们一定要解决在JavaScript中出现的这一系列性能问题。

成都创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于做网站、成都网站建设、乐亭网络推广、微信平台小程序开发、乐亭网络营销、乐亭企业策划、乐亭品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;成都创新互联公司为所有大学生创业者提供乐亭建站搭建服务,24小时服务热线:13518219792,官方网址:www.cdcxhl.com

我们可以通过memoization技术来替代函数中太多的递归调用。memoization是一种可以缓存之前运算结果的技术,这样我们就不需要重新计算那些已经计算过的结果。

对于通过递归来进行计算的函数,memoization简直是太有用了。我现在使用的memoizer是由Crockford写的,主要应用在那些返回整数的递归运算中。当然并不是所有的递归函数都返回整数,所以我们需要一个更加通用的memoizer()函数来处理更多类型的递归函数。

 
 
 
  1. function memoizer(fundamental, cache) { 
  2.   cachecache = cache || {}; 
  3.   var shell = function(arg) { 
  4.       if (! (arg in cache)) { 
  5.           cache[arg] = fundamental(shell, arg); 
  6.       } 
  7.       return cache[arg]; 
  8.   }; 
  9.   return shell; 
  10. }

这个版本的函数和Crockford写的版本有一点点不同。首先,参数的顺序被颠倒了,原有函数被设置为***个参数,第二个参数是缓存对象,为可选参数,因为并不是所有的递归函数都包含初始信息。在函数内部,我将缓存对象的类型从数组转换为对象,这样这个版本就可以适应那些不是返回整数的递归函数。在shell函数里,我使用了in操作符来判断参数是否已经包含在缓存里。这种写法比测试类型不是undefined更加安全,因为undefined是一个有效的返回值。我们还是用之前提到的斐波纳契数列来做说明:

 
 
 
  1. var fibonacci = memoizer(function(recur, n) { 
  2.   return recur(n - 1) + recur(n - 2); 
  3. }, { "0": 0, "1": 1} );

同样的,执行fibonacci(40)这个函数,只会对原有的函数调用40次,而不是夸张的331,160,280次。memoization对于那些有着严格定义的结果集的递归算法来说,简直是棒极了。然而,确实还有很多递归算法不适合使用memoization方法来进行优化。

有的观点认为,任何使用递归的情况,如果有需要,都可以使用迭代来代替。实际上,递归和迭代经常会被作为互相弥补的方法,尤其是在另外一种 出问题的情况下。将递归算法转换为迭代算法的技术,也是和开发语言无关的。这对JavaScript来说是很重要的,因为很多东西在执行环境中是受到限制的。让我们回顾一个典型的递归算法,比如说归并排序,在JavaScript中实现这个算法需要下面的代码:

 
 
 
  1. function merge(left, right) { 
  2.   var result = []; 
  3.   while (left.length > 0 && right.length > 0) { 
  4.       if (left[0] < right[0]) { 
  5.           result.push(left.shift()); 
  6.       } else { 
  7.           result.push(right.shift()); 
  8.       } 
  9.   } 
  10.   return result.concat(left).concat(right); 
  11. }
  12. //采用递归实现的归并排序算法 
  13. function mergeSort(items) { 
  14.   if (items.length == 1) { 
  15.       return items; 
  16.   } 
  17.   var middle = Math.floor(items.length / 2), 
  18.   left = items.slice(0, middle), 
  19.   right = items.slice(middle); 
  20.   return merge(mergeSort(left), mergeSort(right)); 
  21. }

调用mergeSort()函数处理一个数组,就可以返回经过排序的数组。注意每次调用mergeSort()函数,都会有两次递归调用。这个算法不可以使用memoization来进行优化,因为每个结果都只计算并使用一次,就算缓冲了结果也没有什么用。如果你使用mergeSort()函数来处理一个包含100个元素的数组,总共会有199次调用。1000个元素的数组将会执行1999次调用。在这种情况下,我们的解决方案是将递归算法转换为迭代算法,也就是说要引入一些循环:

 
 
 
  1. // 采用迭代实现的归并排序算法 
  2. function mergeSort(items) { 
  3.   if (items.length == 1) { 
  4.       return items; 
  5.   } 
  6.   var work = []; 
  7.   for (var i = 0, 
  8.   len = items.length; i < len; i++) { 
  9.       work.push([items[i]]); 
  10.   } 
  11.   work.push([]); //in case of odd number of items 
  12.   for (var lim = len; lim > 1; lim = (lim + 1) / 2) { 
  13.       for (var j = 0, 
  14.       k = 0; k < lim; j++, k += 2) { 
  15.           work[j] = merge(work[k], work[k + 1]); 
  16.       } 
  17.       work[j] = []; //in case of odd number of items 
  18.   } 
  19.   return work[0]; 
  20. }

这个归并排序算法实现使用了一系列循环来代替递归进行排序。由于归并排序首先要将数组拆分成若干只有一个元素的数组,这个方法更加明确的执行了这个操作,而不是通过递归函数隐晦的完成。work数组被初始化为包含一堆只有一个元素数组的数组。

在循环中每次会合并两个数组,并将合并后的结果放回work数组中。当函数执行完成后,排序的结果会通过work数组中的***个元素返回。在这个归并排序的实现中,没有使用任何递归,同样也实现了这个算法。

分享题目:提升JavaScript递归效率:Memoization技术详解
网页地址:http://www.mswzjz.com/qtweb/news12/168862.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联