试图理解javascript中for循环中的递归

时间:2014-02-13 02:40:23

标签: javascript for-loop recursion

我一直在盯着这个问题的答案,甚至在每次迭代中写下变量和诸如此类的东西。我只是不在这里得到这个过程。当我输入控制台日志时,我看到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
};

2 个答案:

答案 0 :(得分:15)

我会试一试。

概述

从两个具有全局范围的数组开始: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 call permute on [1]. Then we push 1 to usedChars , and remove it from input`中删除。

这给我们留下了四个深度,在步骤(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

所以,你要在递归调用中上下循环,建立一个排列,退回,重建不同的排列,然后退出,直到你循环遍历每个可能的组合。

我希望这有帮助!

答案 1 :(得分:2)

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

  

“每次调用permute时,它都是该函数的新实例   它有自己的封闭权吗?因此变量是变化的   函数内部不会影响其他调用中的变量吗?“

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

变量usedChars在代码中发生了变化,当函数执行递归调用时,代码可以看到该变化,然后变量返回到先前的状态以进行下一次迭代。当函数存在时,变量具有输入函数时的值。

  

“每次调用函数时函数是否返回permArr?”

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