【问题标题】:Trying to understand recursion within for loops in javascript试图理解 javascript 中 for 循环中的递归
【发布时间】:2014-02-13 02:40:22
【问题描述】:

我一直盯着这个问题的答案,甚至在每次迭代中写下变量等等。我只是不明白这里的过程。当我输入控制台日志时,我看到 permute 被称为 input.length - 在它到达这一行之前 1 次 input.splice(i, 0, ch);当我完全迷失时很难说出这个问题,但我猜有些好奇是:每次调用 permute 时,它​​都是该函数的一个新实例,它有自己的闭包,对吧?因此函数内的变量更改不会影响其他调用中的变量?该函数是否在每次调用时都返回 permArr?我想这不一定会影响第一个电话的返回? (我的直觉告诉我,第一次返回时,函数停止运行)。

感谢您的洞察力。

Permutations in JavaScript?

var permArr = [],
usedChars = [];

function permute(input) {
  var i, ch;
  for (i = 0; i < input.length; i++) {
    ch = input.splice(i, 1)[0];
    usedChars.push(ch);
    if (input.length == 0) {
        permArr.push(usedChars.slice());
    }
    permute(input);
    input.splice(i, 0, ch);
    usedChars.pop();
  }
  return permArr
};

【问题讨论】:

标签: javascript for-loop recursion


【解决方案1】:

我会试一试。

概述

您从两个具有全局范围的数组开始:permArray 最终将保存所有排列数组,usedChars 是一个工作数组,用于通过所有递归调用构建每个单独的排列数组。重要的是要注意,这是唯一两个在创建的每个函数的范围内都可以访问的变量。所有其他变量都对它们自己的函数调用具有局部作用域。

然后是递归函数,它接受一个数组作为输入,并返回一个数组数组,其中包含输入数组的所有可能排列。现在,在这个特定的函数中,递归调用在一个循环中。这很有趣,因为终止条件实际上比您的基本递归函数更复杂——当您传入一个空的 input 数组并且 for 循环跳过下一个递归调用时,递归调用终止。

总结

考虑一个四元素数组输入。在高层次上,该函数将遍历这个数组的四个元素,取出每个元素,并计算那个较小的三个元素数组的排列。通过所有这三个元素排列,它会将拉出的原始元素附加到开头,并将这四个元素数组中的每一个添加到permArray

但是,为了找到较小的三元素数组的排列,我们取出每个元素,计算两个元素的较小数组的排列,将取出的元素添加到每个排列的开头,然后返回这三个元素数组中的每一个都在递归调用堆栈中向上,因此可以将原始的第四个元素添加到开头并计为一个排列。

但是,为了找到较小的两个元素数组的排列,我们取出每个元素,计算一个元素的较小数组的排列,将取出的元素添加到每个排列的开头,然后返回这两个元素数组中的每一个都在递归调用堆栈中向上,因此可以将原始的第三个元素添加到该排列的开头并返回堆栈。

但是,为了找到较小的一个元素数组的排列,我们取出元素并计算那个空数组的排列,它刚刚返回,然后我们又将我们的一个元素返回到堆栈中,所以原始的第二个元素可以添加到该排列的开头并返回堆栈。

详情

让我们注意这个函数中的一些步骤:

var permArr = [],
usedChars = [];

function permute(input) {
  var i, ch;
  for (i = 0; i < input.length; i++) {     //   loop over all elements 
    ch = input.splice(i, 1)[0];            //1. pull out each element in turn
    usedChars.push(ch);                    //   push this element
    if (input.length == 0) {               //2. if input is empty, we pushed every element
        permArr.push(usedChars.slice());   //   so add it as a permutation
    } 
    permute(input);                        //3. compute the permutation of the smaller array
    input.splice(i, 0, ch);                //4. add the original element to the beginning 
                                           //   making input the same size as when we started
                                           //   but in a different order
    usedChars.pop();                       //5. remove the element we pushed
  }
  return permArr                           //return, but this only matters in the last call
};

让我们使用数组 [4,3,2,1] 来追溯细节。

当它第一次传入时,我们将取出 4,将其推送到 usedChars,将其从输入中删除,然后在 [3,2,1] 上调用 permute。在此调用中,我们将 3 推送到 usedChars,将其从 input 中删除,然后在 [2,1] 上调用 permute。然后我们将 2 推送到 usedChars,将其从 input, and callpermuteon [1]. Then we push 1 tousedChars, and remove it frominput` 中删除。

这给我们留下了四个深度调用,在步骤 (2) 中: ch=1 输入=[] usedChars=[4,3,2,1]

在第 (2) 步,我们将把第一个排列 [4,3,2,1] 推送到 permArr。然后,继续前进,因为 input 现在为空,所以 (3) 中的递归调用将简单地返回,并且在 (4) 中,我们将简单地将 1 添加回输入并从 usedChars 中删除 1--然后此调用返回。

所以,此时,我们已经开始备份我们的递归调用,并坐在步骤 (4) 中: ch=2 输入=[1] usedChars=[4,3,2]

步骤(4)将执行算法的关键步骤:移动动作。它采用ch=2 并将其添加到input开头 并从usedChars 中删除它。这意味着在步骤(5)之后,我们有:
ch=2 输入=[2,1] usedChars=[4,3]

现在看看我们在哪里。我们将 [4,3,2,1] 作为排列推送,然后备份并交换 2 和 1,现在我们将返回递归调用来构建 [4,3, 1,2] 并将其添加为排列。之后,我们将退出更多,移动更多元素,然后返回排列并添加它们。

回到它,在执行步骤 (5) 之后,我们循环。这意味着我们会将 1 推送到 usedChars 并使用 input=[2] 进行递归调用。该调用会将 2 推入 usedChars,创建一个完整的数组 [4,3,1,2] 并将其添加到 permArray

因此,您将在递归调用中上下循环,建立一个排列,退出,重建一个不同的排列,然后退出,直到您循环了所有可能的组合。

我希望这会有所帮助!

【讨论】:

  • 嗨瑞克。感谢您的详细解释。我想问 - 所以,此时,我们已经开始备份我们的递归调用并坐在步骤 (4) 中:ch=2 input=[1] usedChars=[4,3,2]我>。这里的ch怎么变成2了?
  • 一年后...但我认为 ch 变为 2 的原因是当我们“备份”时,由于我们递归调用函数,每个调用都包含自己的变量。因此,当您调用 permute(input) 时,它会向 ch 添加一个字符,但是当它完成 4 次时,它开始备份,每次返回 permArr 时,它都会返回到上一个调用。因此,如果最后一次调用是 ch=1,那么之前的调用将是 ch=2,之前的调用将是 ch=3,而在这之前的调用将是 ch=4。如果我错了,请纠正我。
【解决方案2】:

代码有点难以理解,因为它混合了循环和递归。它使用一个全局变量 (usedChars),在每次调用期间都会更改和恢复。

"每次调用 permute 时,它​​都是该函数的一个新实例 用它自己的关闭对吗?因此变量的变化是 函数内不会影响其他调用中的变量?”

嗯,是的,也不是。每个函数调用都有自己的作用域,但由于没有需要捕获的变量,因此没有闭包。局部变量 ich 以及参数 input 是作用域的局部变量,因此每个调用都有自己的这些变量集。任何其他变量都是全局变量,因此它们在所有调用之间共享。

变量usedChars在代码中发生了变化,当函数进行递归调用时,该变化对代码是可见的,然后变量被改回之前的状态以进行下一次迭代。当函数存在时,变量的值与输入函数时相同。

"函数是否每次调用都返回 permArr?"

是的,但是当函数调用自身时,返回值被忽略。只有当数组从最外层调用返回时才会使用它。

【讨论】:

    猜你喜欢
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    • 2017-12-06
    • 1970-01-01
    • 2023-03-26
    • 2011-06-15
    • 2016-07-24
    • 2015-07-14
    相关资源
    最近更新 更多