【问题标题】:How does return statement return values in recursive functions in JavaScript?JavaScript 递归函数中的 return 语句如何返回值?
【发布时间】:2018-06-28 15:27:17
【问题描述】:

谁能解释一下这个递归函数是如何工作的。我不明白这些值是如何在调用堆栈中返回的。您能否用一些代表执行上下文的图形进行解释。如果你能解释一下 Udemy 课程 JavaScript 中解释的方式会更好 - 理解奇怪的部分。

function findSolution(target) {

    function find(start, history) {
        if (start == target)
            return history;
        else if (start > target)
           return null;
        else
            return find(start + 5, "(" + history + " + 5) ") || find(start * 3, "(" + history + " * 3) ");
    }

return find(1, "1");
}
console.log(findSolution(13));

【问题讨论】:

  • 您可以查看此视频,确切时间有动画youtu.be/8aGhZQkoFbQ?t=326
  • 理解递归的一个关键是:它并不特别。如果您了解function three() { return one(two()); } 的工作原理,那么您就会了解function recurse(val) { return val == 0 ? 0 : 1 + recurse(val - 1); } 的工作原理。
  • 如果您在理解递归时遇到困难(这完全可以理解!)我建议从一个更简单的递归函数开始,例如我上面评论中的那个,并使用您的 IDE 中内置的调试器或浏览器来单步执行,查看调用堆栈等。调试器提供了一个实时图表来说明正在发生的事情。
  • 谢谢,但我想知道每次函数执行时这些值是如何返回的。
  • @AarizzzHakim:一个好的调试器会告诉你这一点。如果您使用的调试器只是马马虎虎,只需在返回之前将值分配给局部变量,您就可以看到它。

标签: javascript recursion return


【解决方案1】:

简单来说,

递归函数是在执行过程中调用自身的函数。这使得函数可以重复多次,输出结果和每次迭代的结束。

递归函数在计算机科学中很常见,因为它们允许程序员使用最少的代码编写高效的程序。

不利的一面是,如果编写不当,它们可能会导致无限循环和其他意外结果。

递归函数的开发和执行是基于复杂性和编程性质的变体。如果我们只是假设要对任何高达 100 的值进行递归递增,那么它可能真的很容易实现,但是在实际的编程环境中,我们面临着很多复杂的问题。

function increment(start){    
    if(start < 100){
        start++;
        increment(start);     
        console.log(start);
    }else{
        console.log("value reached to 100");
    }
}
increment(0);

在这段代码中,我使用了递增 1 的增量函数。

increment(0);

这是我可以将初始值分配为 0 的函数调用,它将在函数中以 1 开始递增。

if(start < 100){
    start++;
    increment(start);     
    console.log(start);
}else{
    console.log("value reached to 100");
}

我将比较限制设置为 100,因此我的函数调用了 101 次。一旦达到限制并且(start

如果我没有给出 100 的限制,那么它会进入无限循环,并且 javascript 调用堆栈最大大小超出错误或浏览器可能会进入无限循环。

希望我的解释对你有所帮助。请查看对您了解递归和递归方法肯定有帮助的网站。

http://pages.cs.wisc.edu/~calvin/cs110/RECURSION.html

https://javascript.info/recursion

【讨论】:

  • 关于递归的很好解释。但我认为 OP 要求解释这个特定函数是如何工作的(而不是一般的递归是如何工作的)
  • @ChiragRavindra 可能你可能错了,请仔细阅读问题,他没有具体询问所描述的递归函数,但想知道递归的概念。
  • 来自问题:“有人可以解释一下这个递归函数是如何工作的。”不是我的反对票,干杯:)
  • @Chirag Ravindra 是的,兄弟,你就在这里。我只是问这个功能是如何工作的。实际上不是递归的概念。
【解决方案2】:

你所拥有的是一个递归函数,它有两个退出条件,一个是找到的结果,起始值和目标值相等,这将返回历史记录,另一个是起始值大于目标值,它返回 @ 987654325@ 未找到结果。

递归机制是将函数的调用分向两个方向,一个是加法,另一个是将值乘以三。

分支以logical OR || 进行,这意味着如果第一个分支返回truthy 结果,则采用该结果,并且永远不会调用另一个分支。如果第一个分支返回null,一个falsy 值,则调用第二个分支。

你最终得到的是一棵带有分支和测试的树。

      1          level 0
    /   \
  2       5      level 1
 / \     / \
3   4   6   7    level 2

这意味着,第一次调用时,它从 1 开始,必要时转到 2 和 3,依此类推。

要了解一下,您可以将级别变量添加到 console.log 并观察每个级别的情况。

level, start, history                         action
--------------------------------------------  ----------------
0 1 1                                         check next level
    1 6 (1 + 5)                               check next level
        2 11 ((1 + 5)  + 5)                   check next level
            3 16 (((1 + 5)  + 5)  + 5)        null
            3 33 (((1 + 5)  + 5)  * 3)        null
        2 18 ((1 + 5)  * 3)                   null
    1 3 (1 * 3)                               check next level
        2 8 ((1 * 3)  + 5)                    check next level
            3 13 (((1 * 3)  + 5)  + 5)        found

(((1 * 3)  + 5)  + 5)                         result

function findSolution(target) {

    function find(start, history, level) {
        level = level || 0;
        console.log(level, start, history);
        if (start == target) return history;            // exit condition 1 (found)
        if (start > target) return null;                // exit condition 2 (not found)
        return find(start + 5, "(" + history + " + 5) ", level + 1) ||
               find(start * 3, "(" + history + " * 3) ", level + 1);
    }

    return find(1, "1");
}
console.log(findSolution(13));
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 谢谢,非常好。您能否告诉我何时返回 null,它如何移动到逻辑 OR 的右侧并选择将“开始”乘以 3。我很困惑,如果它可能会将 null 返回到主函数并停止递归.
  • null 与逻辑或强制下一个表达式求值。如果你有null || 42,你就会得到答案。
  • @AarizzzHakim 关于短路评估的详细博客jstips.co/en/javascript/short-circuit-evaluation-in-js
  • Nice hack using .as-console-wrapper { max-height: 100% !important; top: 0; } 想知道为什么这里需要 CSS,直到我意识到它将控制台小部件的样式设置为全屏
【解决方案3】:

已尝试在下面列出一些执行案例供您参考。我想这里唯一棘手的部分是return find(start + 5, "(" + history + " + 5) ") || find(start * 3, "(" + history + " * 3) ");

可能不明显的是|| 被称为short circuit or in JS。这意味着如果我们这样做 var x = a || b b 只有在 a 不真实时才会被评估。

在您的问题的上下文中,上述行基本上是在说“尝试沿着加 5 的路径前进,并且只有在失败时(即返回 null)尝试沿着乘 3 的路径前进。

这里的递归函数find基本上有三种情况:

  1. start == target 为真,这意味着我们已经通过一个解决方案到达递归的终点。这将返回历史字符串
  2. start &gt; target 为真,表示我们已经超出了目标,返回 null 表示这条路径没有通向解决方案
  3. 这两种情况均无效。 Try going down the path of adding 5。如果这不起作用(我们返回 null),try going down the path of multiplying 3

示例案例:

findSolution(0) //target=0
    find(1, "1") //This goes to Case 3
        find(1+5,"(1 + 5)") || find(1*3,"(1 * 3)")
            find(1+5,"(1 + 5)") // This is a Case 2, returns null, try multiplying by 3 now
            find(1*3,"(1 * 3)") // This is Case 2 again, returns null
        null || null //Both paths lead to null, stop recursing, return null
    null
null



findSolution(1) //target=1
    find(1,"1") // This leads to the case 1. Return the history string
    "1"
"1"

findSolution(3) //target = 3
    find(1,"1") //This leads to case 3
        find(1+3,"(1 + 5)") || find(1*3,"(1 * 3)")
            find(1+5,"(1 + 5)") // This is a Case 2, returns null, try multiplying by 3 now
            find(1*3, "(1 * 3)") // This is a case 1, return History string, recursion ends
        null || "(1 * 3)"
    "(1 * 3)"
"(1 * 3)"

findSolution(8) //target=8
    find(1,"1") //This leads to case 3
        find(1+5,"(1 + 5)") || find(1*3,"(1 * 3)") // Evaluate lhs first
            find(1+5,"(1 + 5)") // This is a case 3 scenario again
                find(6 + 5,"((1 + 5) + 5)") || find(6 * 3, "((1 + 5) * 3)")
                    find(6 + 5,"((1 + 5) + 5)") // Case 2, returns null
                    find(6 * 3, "((1 + 5) * 3)") // Case 2, returns null
                null || null //Going down the add 5 path failed here, try multiplying 3
            find(1*3,"(1 * 3)") // This is a case 3
                find(3 + 5, "((1*3)+5)") || find(3 * 3, "((1 * 3) * 3")
                    find(8,"((1*3)+5)")  // This is case 1, recursion ends, return history
                    "((1*3)+5)"
                "((1*3)+5)" || find(..) //LHS evaluated to true, no need to do rhs
            "((1*3)+5)"
        "((1*3)+5)"
    "((1*3)+5)"
"((1*3)+5)"

【讨论】: