无法理解 freeCodeCamp 中的 JS 递归倒计时函数

2024-01-27

所以,我已经明白了ideaJavaScript 中的递归(有一个功能是loops itself直到达到base条件,此时它停止并且返回最终结果),但是当我将其应用于尝试将其应用于的实际语法时,我有点头疼创建数组.

让我们使用freeCodeCamp 的倒计时练习 https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/basic-javascript/use-recursion-to-create-a-countdown举个例子。这是正确的结果:

function countdown(n) {
  if (n < 1) {
    return [];
  } else {
    const arr = countdown(n - 1);
    arr.unshift(n);
    return arr;
  }
}

这里的想法显然是有一个函数从给定的数开始倒数n to 1。我理解这个想法,但我有几个问题:

1 -为什么是数组声明为常量 and 分配给递归调用?数组不应该更好地声明吗outside as a variable(因为它changesvalue) – 并且它的当前位置是否意味着它已被声明(并被覆盖)每次发生递归吗?

2 -我得到了当前值的不变,但为什么要声明它after递归调用?不应该定位吗before(上面一行)以便取消移动值before函数循环 itself?

3 -同样,为什么返回完整的最终数组在 else 循环内?是不是应该把它还到外面比较好,或者在上述基本条件下,以免被退回每次函数循环?

事实上,我的理解是,当n达到 0 时,循环应返回empty数组,以及不是已创建的完整数组与递归。

~ ~ ~

最终编辑/理解:感谢每一个回答!所以,如果我目前的理解是正确的,递归不循环这个函数,正如我最初所想的那样——相反,它有点像"opens"的一个电话“链式函数”这些都不在了向下直到找到并且解决基本情况.

当那个"base"案例或功能得到解决,链条继续运行"up",可以这么说,解决上面的每个递归步骤, .不变每个 n 值,并返回结果数组“向上”,直到我们到达初始n值,此时返回最终数组,并且退出该函数。

我想我现在(有点)明白了!实际上编写递归函数并不容易,但我更好地掌握了这个过程的实际发生方式。


为什么将数组声明为常量并分配给递归调用?

它不是。该数组是可变的。每次调用此函数都会返回一个新数组。没有数组被“覆盖”。const在 JavaScript 中仅用于声明一个常数变量它具有词法范围(即变量arr之后不复存在return arr) 并且是constant(不能变异/分配)。

我得到了当前值的不变,但为什么它是在递归调用之后声明的?难道它不应该定位在之前(上面一行)以便在函数循环本身之前取消移位值吗?

您不能将其放置在上面,因为需要递归调用才能获得arr首先。当你倒计时后n-1 to 1,你必须前置n在第一个位置,这就是unshift does.

同样,为什么在 else 循环中返回完整的最终数组?难道不是应该更好地在外部返回,或者在上面的基本条件上返回,以免每次函数循环时都返回吗?

不存在“else循环”这样的东西。在那里返回数组的原因是你想要countdown(n)返回刚刚生成的数组,使用数组countdown(n-1).

事实上,我的理解是,当 n 达到 0 时,循环应该返回一个空数组,而不是通过递归创建的完整数组。

正是如此if (n < 1) return []; does.


也就是说,对于手头的任务,给出的递归实现是高度次优。首先,你不需要else since return无论如何都会退出该函数:

function countdown(n) {
  if (n < 1) return [];

  const arr = countdown(n - 1);
  arr.unshift(n);
  return arr;
}

其次,这是最具可读性(和性能)的简单for loop:

function countdown(n) {
  const arr = Array(n)
  for (let i = 0; i < n; i++) arr[i] = n - i;
  return arr;
}

(奖励:可以使用提前知道的数组大小来利用Array构造函数以防止 JS 在推送元素时调整数组大小)。

三、目前的实施情况O(n²) 的二次时间复杂度因为arr.unshift是应用的线性时间运算n时间到长度数组0 to n-1.

递归在以下情况下非常有用:需要堆栈(例如深度优先遍历)。创建倒计时不需要堆栈。这不是using反而abusing递归来实现高度次优的算法。如果您 - 无论出于何种原因(也许您被迫使用没有循环,只有递归的纯函数式语言?) - 想要替换完美的罚款for不惜一切代价进行递归调用循环,只需使用尾递归和词法范围(闭包):

function countdown(n) {
  const arr = [];
  function loop(n) {
    if (n < 1) return;
    arr.push(n);
    loop(n-1);
  }
  loop(n);
  return arr;
}

或者如果你不喜欢闭包,那么可选的怎么样?arr范围?这允许扭转递归,因为调用者现在拥有arr在被调用者之前,因此可以在后续递归调用执行相同操作之前附加其编号:

function countdown(n, arr = []) {
  if (n < 1) return arr;
  arr.push(n);
  countdown(n - 1, arr);
  return arr;
}

结果相同,但(IMO)更干净,最重要的是,代码速度明显更快。尝试运行所有这些功能n = 1e6您会发现 freeCodeCamp 未能在合理的时间内完成,而其他则几乎立即完成。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

无法理解 freeCodeCamp 中的 JS 递归倒计时函数 的相关文章

随机推荐