深入了解JavaScript中递归的理解与实现

目录
  • 前言
  • 递归的基本理解
  • 实例解析
    • 求斐波那契数
    • 时间复杂度分析
    • 空间复杂度分析
    • 执行顺序分析

前言

我们在写业务代码的时候,或多或少都会遇到需要使用递归的场景,比如在遍历树形结构时。

本文将通过递归的经典案例:求斐波那契数来讲解递归,通过画递归树的方式来讲解其时间复杂度和空间复杂度以及递归的执行顺序,欢迎各位感兴趣的开发者阅读本文。

递归的基本理解

表象理解

  • 函数会自己调用自己
  • 每一次调用,函数的参数都会收敛变小

实质理解

  • 把一个大问题变成1个或n个小问题
  • 用同样的逻辑来解决这些问题
  • 最后把他拼凑起来,拼成全局问题

具体实现

  • 先写Base case,定义基线条件,判断其是否为最小号问题,避免死循环
  • Recursive rule:递归规则

实例解析

接下来我们通过一个实例来讲解递归的应用。

求斐波那契数

求特定位置的斐波那契数,用递归实现代码很简单,接下来我们先看下斐波那契数的概念。

  • 0号位置的斐波那契数是0
  • 1号位置的斐波那契数是1
  • n(n>1)号位置的斐波那契数等于 n-1位置的斐波那契数 + n-2位置的斐波那契数

我们知道怎么计算斐波那契数后,就可以用递归来将其实现了。

我们可以将上述递归的理解中应用到求斐波那契数里,实现思路和实现代码如下:

  • Base case: 0号位置的斐波那契数是0,1号位置的斐波那契数是1。即:n === 0 return 0, n === 1 return 1;
  • Recursive rule: n号位置的值 = n - 1位置的值 + n - 2位置的值,即:fibonacciNumbers(n - 1) + fibonacciNumbers(n - 2);
const fibonacciNumbers = function(n){
    // base case
    if(n === 0){
        return 0;
    }else if(n === 1){
        return 1;
    }

    // Recursive rule
    return fibonacciNumbers(n - 1) + fibonacciNumbers( n - 2);
}

时间复杂度分析

我们将上述代码执行过程转换成如下图所示的递归树,观察二叉树中的节点后我们发现如下规律:

  • 第0层有1个节点,第1层有2个节点,第2层有4个节点,第3层...第n层,每一层的节点数都是上一层的2倍。
  • 即:1 + 2 + 4 + 8 + 2^(n-1),等比数列求和后:2^n,时间复杂度为:O(2^n)
  • 最后一层结点的总数,远远超过其他所有层的总数。
  • 时间复杂度取决于递归树中一共有多少节点。
  • 所有递归的时间复杂度都可以通过递归树来分析。

空间复杂度分析

分析空间复杂度我们可以通过递归的执行顺序来分析,我们将上述代码的执行顺序整理成递归图标示其执行顺序,我们发现如下规律:

  • 由于冯诺伊曼体系的影响,递归树执行时采用深度优先的方式执行。即:顺着一条线执行到底(蜜橙色线条)。
  • 图中每一层执行时的bp全称为:break point,每一层执行到bp时,会将当前层的变量(n)记录一下,放进Call stack中。
  • 由于执行递归树中的每一层时,都会有一个Call stack操作,将当前层的变量(n)放进去,因此递归树中有多少个调用栈取决于递归树的层数,因此空间复杂度为O(n)
  • 空间复杂度与节点总数关系不大,与其在Call stack里总共存了多少层直接相关。
  • 所有递归的空间复杂度都可以通过递归树来分析。

执行顺序分析

上述递归图的执行顺序如下图所示,接下来带着代价来分析下每一步都做了哪些事情:

  • 当函数执行到return fibonacciNumbers(n - 1) + fibonacciNumbers( n - 2) 的时候,由于冯诺伊曼体系的影响,它不会并行执行,他会先执行fibonacciNumbers(n - 1)函数,触发基线条件时,return到上一层,取出其在上一层在call Stack中存储的n的值,然后再去执行fibonacciNumbers( n - 2)函数,计算它右子树的值。
  • 因此他会先执行fibonacciNumbers(n - 1)函数,即:F(4) => F(3) ... =>F1(图中的第1行)
  • 当他执行到F(1)的时候,n = 1,触发基线条件return 1返回到上一层F(2),即图中的第2行
  • 返回到F(2)层时,取出当前层Call Stack中存储的n的值,执行fibonacciNumbers(n - 2)函数,执行到F(0),即图中的第3行
  • 此时F(0)中n的值为0,触发基线条件,return 0,即图中的第4行
  • 此时(2)节点的左子树和右子树的值都计算出来了,因此可以执行fibonacciNumbers(n - 1) + fibonacciNumbers( n - 2)函数,将左、右子树的值相加,即得到了F(2)的值,然后return至上一层F(3),即图中的第5行。
  • 返回到F(3)时,与第3步一样,获取其右子树的值,然后重复第3至6步的步骤,直至计算出F(3)和F(2)的值,将其相加就得出了F(4)的值,此时F(4)处的值就是我们需要求的斐波那契数,即图中的第6~16行。

以上就是深入了解JavaScript中递归的理解与实现的详细内容,更多关于JavaScript递归的资料请关注我们其它相关文章!

(0)

相关推荐

  • JavaScript中递归实现的方法及其区别

    递归函数:递归函数是在通过名字调用自身的情况下构成的. 递归实现阶乘函数: 方法一:通过使用函数的名字 function factorial(num){ if(num<=1){ return 1; }else{ return num*factorial(num-1); } } console.log(factorial(4)); 结果为:24: 但是这种方法实现递归有一个问题,观察以下代码: function factorial(num){ if(num<=1){ return 1; }els

  • JavaScript递归函数定义与用法实例分析

    本文实例讲述了JavaScript递归函数定义与用法.分享给大家供大家参考,具体如下: 递归函数是一个函数通过名字调用自身的情况下形成的,比如经典的递归阶乘函数: function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } 上面的这种写法,可能会造成问题: var anotherFactorial = factorial; factorial = null; c

  • javascript中递归的两种写法

    话不多说,请看代码 function addd(n){ if(n==1){ return 1; } return n*addd(n-1); } function add(n){ var num=1; for(var i=1;i<n;i++){ num=num*i; } return num; } 以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我们!

  • JavaScript递归操作实例浅析

    本文实例分析了JavaScript递归操作.分享给大家供大家参考,具体如下: 问题 一个简单的递归,求n的阶乘: function factorial(n){ if (n<=1) { return 1; }else{ return factorial(n-1)*n; } } 如果像下面这样使用它,则会出错: var fcopy = factorial; factorial = null; alert(fcopy(3)); 因为fcopy指向的函数实体调用了factorial,而factorial

  • 关于JavaScript递归经典案例题详析

    目录 什么是递归,它是如何工作的? 一.求和 (1)数字求和 (2)数组求和 二.数据转树 三.汉诺塔 四.斐波那契数列 总结 什么是递归,它是如何工作的? 我们先来看一下递归(recursion)的定义: 递归是一种解决问题的有效方法,在递归过程中,函数将自身作为子例程调用. 简单说程序调用自身的编程技巧叫递归.递归的思想是把一个大型复杂问题层层转化为一个与原问题规模更小的问题,问题被拆解成子问题后,递归调用继续进行,直到子问题无需进一步递归就可以解决的地步为止. 使用递归需要避免出现死循环,

  • JavaScript递归详述

    目录 一.什么是递归? 二.利用递归求数学题 1.求1 * 2 * 3 * 4 -*n的阶乘 2. 求斐波那契数列 三.利用递归求对应数据对象 一.什么是递归? 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数.简单理解:函数内部自己调用自己, 这个函数就是递归函数. 如下所示: function fn(){ fn(); } fn(); 这个函数就是一个递归函数,当我们直接打印时,会: 发现打印错误,这是为什么呢?因为递归函数的作用和循环效果一样.当没有给他返回值的时候,它就会一直死循

  • 深入了解JavaScript中递归的理解与实现

    目录 前言 递归的基本理解 实例解析 求斐波那契数 时间复杂度分析 空间复杂度分析 执行顺序分析 前言 我们在写业务代码的时候,或多或少都会遇到需要使用递归的场景,比如在遍历树形结构时. 本文将通过递归的经典案例:求斐波那契数来讲解递归,通过画递归树的方式来讲解其时间复杂度和空间复杂度以及递归的执行顺序,欢迎各位感兴趣的开发者阅读本文. 递归的基本理解 表象理解 函数会自己调用自己 每一次调用,函数的参数都会收敛变小 实质理解 把一个大问题变成1个或n个小问题 用同样的逻辑来解决这些问题 最后把

  • 谈谈JavaScript中function多重理解

    JavaScript 中的 function 有多重意义.它可能是一个构造器(constructor),承担起对象模板的作用: 可能是对象的方法(method),负责向对象发送消息.还可能是函数,没错是函数,和对象没有任何关系独立存在的可以被调用的函数. 由于语言设计者的妥协,在 JavaScript 加入了一些 class 相关的特性,以使 JavaScript 看起来确实象 Java,可以 "面向对象".虽然 JavaScript 添加了 new 和 this, 但却没有 clas

  • 全面理解JavaScript中的闭包

    引子 闭包是有权访问另一个函数作用域中的变量的函数. 闭包是javascript中很难理解的部分,很多高级的应用都依靠闭包来实现的,我们先来看下面的一个例子: function outer() { var i = 100; function inner() { console.log(i); } } 上面代码,根据变量的作用域,函数outer中所有的局部变量,对函数inner都是可见的:函数inner中的局部变量,在函数inner外是不可见的,所以在函数inner外是无法读取函数inner的局部

  • AJAX入门之深入理解JavaScript中的函数

    概述 函数是进行模块化程序设计的基础,编写复杂的Ajax应用程序,必须对函数有更深入的了解.JavaScript中的函数不同于其他的语言,每个函数都是作为一个对象被维护和运行的.通过函数对象的性质,可以很方便的将一个函数赋值给一个变量或者将函数作为参数传递.在继续讲述之前,先看一下函数的使用语法: function func1(-){-}var func2=function(-){-};var func3=function func4(-){-};var func5=new Function()

  • 理解JavaScript中Promise的使用

    Javascript 采用回调函数(callback)来处理异步编程.从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验.于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题.本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解. 什么是 Promise 一个 Promise 对象代表一个目前还不可用,但是在未来的

  • JavaScript中关于递归与回溯的实例详解

    目录 何为递归 构成递归条件 关于回溯 实际业务 组合问题 何为递归 递归作为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量.递归的能力在于用有限的语句来定义对象的无限集合.需要注意的是,递归必须要用边界条件,否则很容易导致死循环 构成递归条件 子问题须与原始问题为同样的事,且更为简单

  • 深入理解javascript中的立即执行函数(function(){…})()

    javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解. ( function(){-} )()和( function (){-} () )是两种javascript立即执行函数的常见写法,最初我以为是一个括号包裹匿名函数,再在后面加个括号调用函数,最后达到函数定义后立即执行的目的,后来发现加括号的原因并非如此.要理解立即执行函数,需要先理解一些函数的基本概念.

  • 深入理解javascript中的 “this”

    一.前言: 我们知道 "this" 是javascript语言的一个关键字,在编写javascript代码的时候,经常会见到或者用到它. 但是,有一部分开发朋友,对 "this" 一知半解,下面我们就一起来探讨学习下javascript中 "this" 的具体含义吧! 二.This总结: This指针作用域: 1).在全局执行环境中使用this,表示Global对象,在浏览器中就是window对象. 2).当在函数执行环境中使用this时,情况就

随机推荐