【问题标题】:How exactly does this recursive function work in JavaScript?这个递归函数在 JavaScript 中究竟是如何工作的?
【发布时间】:2016-11-22 13:29:18
【问题描述】:

我有以下递归函数的例子,我不明白的是事情发生的顺序:

function power(base, exponent) {
  if (exponent == 0)
    return 1;
  else
    return base * power(base, exponent - 1);
}

函数什么时候返回值,在所有过程结束时还是每次?

【问题讨论】:

  • 你可以只记录参数。例如,在声明电源后立即添加console.log(base,exponent)。一切都将揭晓!
  • @TJHeuval:更好的是,使用适当的调试器进行检查。 printf 式调试在 2011 年几乎没有地位! :-)
  • 离题,但请注意,如果传递负指数,您的 power 函数将失败。它会以越来越低的负指数反复调用自己,直到达到递归限制并以“递归过多”错误退出。在其他一些环境中,我们知道它的另一个名称:stack overflow! ;-)

标签: javascript recursion


【解决方案1】:

一般可视化递归中发生的事情的一种简单方法是:

  1. 一个堆栈函数调用被创建:这个过程需要一个适当的终止条件来结束(否则你将有无限递归,这是邪恶
  2. 单个结果出栈:每个结果用于计算下一步,直到堆栈为空

即如果 base=5 和 exponent=3,调用堆栈是(顶部的最后一个元素):

5*(5*(5*1))
5*(5*(5*power(5, 0)))
5*(5*power(5, 1))
5*power(5, 2)
power(5, 3)

那么每个被调用的函数都有实参并准备返回一个值(顶部的第一个元素):

5*(5*(5*1))
5*(5*5)
5*25
125

请注意,这里的函数是按顺序计算的:首先是power(5, 0),然后是power(5, 1),依此类推。每次计算后,堆栈的一个元素被释放(即内存被释放)释放)。

希望对你有帮助:)

【讨论】:

  • 因此,如果理解正确,它将采用power(3,3) 并将第一个返回3 * power(3,2); 然后3 * power(3,1); 放入堆栈,直到它满足停止条件,这是一个最终数字而不是另一个函数,并从头到尾计算栈中的所有函数?
  • 是的;顺便说一句,无限(或太深)递归是 well known 堆栈溢出错误 (stackoverflow.com/questions/214741/… :) 的最常见原因之一
【解决方案2】:

通常有助于理解诸如此类的递归函数,以便像在代数课中一样解决问题。考虑:

power(3, 4) 
= 3 * power(3, 3)
= 3 * (3 * power(3, 2))
= 3 * (3 * (3 * power(3, 1)))
= 3 * (3 * (3 * (3 * power(3, 0))))
= 3 * (3 * (3 * (3 * 1)))
= 3 * (3 * (3 * 3))
...
= 81

【讨论】:

  • 递归函数有点像循环。您需要定义一个中止条件。您的条件是指数变为 1。
  • @Ray Toal,所以在函数到达它的基本情况之前,它会堆叠函数,当它有一个最终数字而不是另一个函数时,它会从上到下计算所有堆叠的函数?
  • 是的,在这种情况下,函数是“堆叠的”,在达到基本情况后,它们会“展开”并应用乘法。答案中括号的嵌套显示了堆叠和展开,虽然有点神秘,但它就在那里。
  • 虽然链接中的示例使用的是斐波那契函数,但解释很适合理解递归函数的工作原理https://www.youtube.com/watch?v=zg-ddPbzcKM
【解决方案3】:

这里的关键是power 完全按照它可以调用任何其他函数的方式调用自身。所以当它这样做时,它会等待函数返回并使用它的返回值。

如果你这样做了

var x = power(10, 2);
  1. 您对power 的呼叫将转到此行:

    return base * power(base, exponent - 1)
    

    ...并致电power(10, 1),等待其返回。

  2. power(10, 1) 的调用当然会到达线路:

    return base * power(base, exponent - 1)
    

    ...并致电power(10, 0),等待其返回。

  3. power(10, 0) 的调用将返回1,然后上面#2 中的调用使用它来完成它的工作并返回10 * 1 = 10,然后让你原来的调用在上面的#1 中返回值10 * 10 = 100

当试图理解这样的事情时,没有什么比使用调试器遍历代码更有趣了。在这个现代世界中,you have plenty to choose from,其中许多可能已经在您的计算机上。

【讨论】:

    【解决方案4】:

    为了更好的可视化,只需将函数调用替换为函数体(例如可能是伪代码)。

    function power(base, exponent) {
      if (exponent == 0)
        return 1;
      else
        return base * power(base, exponent - 1);
    }
    

    power(5, 3) 扩展至此

    function power(5, 3) {
        // exponent 3 is not 0
    
        // return 5 * power(5, 3-1)
        return 5 * function power(5, 2) {
            // exponent 2 is not 0
    
            // return 5 * power(5, 2-1)
            return 5 * function power(5, 1) {
                //exponent 1 is not 0
    
                // return 5 * power(5, 1-1)
                return 5 * function power(5, 0){
                    //exponent 0 is 0
                    return 1;
                }
            }
        }
    }
    

    现在画面清晰了。一切都变成了下面这样..

    // 1
    function power(5, 3){
        return 5 * function power(5, 2){
            return 5 * function power(5, 1){
                return 5 * ( function power(5, 0){
                    return 1;
                } )
            }
        }
    }
    
    // 2
    function power(5, 3){
        return 5 * function power(5, 2){
            return 5 * ( function power(5, 1){
                return 5 * 1;
            } )
        }
    }
    
    // 3
    function power(5, 3){
        return 5 * ( function power(5, 2){
            return 5 * 5 * 1;
        } )
    }
    
    // 4
    function power(5, 3){
        return ( 5 * 5 * 5 * 1 );
    }
    
    // 5
    5 * 5 * 5 * 1;
    

    【讨论】:

      【解决方案5】:

      与任何递归函数一样,特定“实例”的返回发生在计算返回值时。这意味着递归版本将被计算出来。

      因此,如果您传入一个 4 的指数,那么在某一时刻将有 4 个函数的副本同时被执行。

      【讨论】:

        【解决方案6】:

        这条线和它的分辨率真的让我大吃一惊:

        return base * power(base, exponent - 1)
        

        我知道指数是递减的,直到它满足基本情况,但是当你乘以 基数乘以递归函数调用,我一直在想“函数如何自己乘以基数(基数论证)?”,它到底在哪里做,因为调用 base * power(base, exponent - 1) 没有' t 看起来像标准循环结构。它怎么会调用一个有两个参数的函数,它怎么知道跳过指数参数并将基数乘以基数?

        【讨论】:

          【解决方案7】:

          从数学角度:

          让 x = 基数, 让 n = 指数

          x*x^(n-1) = x^n

          因为

          x^1*x^n-1=x^n(同类项的指数相加)

          同理:

          base * base*exponent-1.
          

          【讨论】:

            猜你喜欢
            • 2021-01-27
            • 1970-01-01
            • 2013-03-09
            • 2013-07-06
            • 2019-01-05
            • 1970-01-01
            • 1970-01-01
            • 2021-07-05
            • 1970-01-01
            相关资源
            最近更新 更多