这个递归数组置换函数如何在幕后工作?

Posted

技术标签:

【中文标题】这个递归数组置换函数如何在幕后工作?【英文标题】:How does this recursive array permutation function work under the hood? 【发布时间】:2016-12-18 20:10:27 【问题描述】:

此函数生成数组的排列。我已经在纸上写下了断点,并在开发工具中设置了断点,并煞费苦心地逐步完成了每个函数调用,但我仍然不明白这是如何工作的。

特别是 for 循环。一旦 do It 函数拼接出数组中的所有数字,它将临时数组的切片副本推送到答案数组中。然后它将项目拼接到参数数组中,从临时数组中弹出相同的项目并返回 for 循环第一次迭代的答案。因此,在遍历数组一次后,答案 = [1,2,3] temp = [1,2] 和 arr =[3]。

这是我迷路的地方。它似乎跳回到拼接处并将 2 拼接回 arr。在 devTools 中,我对 i、item、temp 和 arr 进行了监视。它说我以某种方式变成了 1,即使在我们将 3 拼接回它之后,arr 中只有一个项目。如果长度为 1 并且 for 循环指定它应该在 arr.length 处停止运行,它如何以某种方式跳回将 2 拼接回数组中?

如果我措辞不连贯,我很抱歉。我今天花了很多时间来研究这个。

Tdlr.运行这个函数。在 do It 函数的 for 循环中放置一个断点。一旦数组为空并且我们返回答案,它如何将两个项目拼接回原始 arr。

function permute(arr) 
    var temp = [];
    var answer = [];

    function logResult() 
      answer.push(
        // make a copy of temp using .slice()
        // so we can continue to work with temp
        temp.slice()
      );
    

    function doIt() 
      var i;
      var len;
      var item;

      for (i = 0, len = arr.length; i < len; i++) 
        // remove the item at index i
        item = arr.splice(i, 1)[0];
        // add that item to the array we're building up
        temp.push(item);
        if (arr.length) 
          // if there's still anything left in the array,
          // recurse over what's left
          doIt();
         else 
          // otherwise, log the result and move on
          logResult();
        
        // restore the item we removed at index i
        // and remove it from our temp array
        arr.splice(i, 0, item);
        temp.pop();  
      
      return answer;
    
  return doIt();
;

console.log(permute([1,2,3]));

谢谢!

【问题讨论】:

递归!每个调用都有自己的范围,有自己的变量和值。当你“跳跃”时,并不是变量神奇地改变了,它只是不同的变量(在递归调用的上下文中)。 调试这个的时候,还要留意栈。 好的,会的。谢谢伯吉! @Eliot 只是想指出代码中存在语法错误,并且 permute 函数也没有返回任何内容。我相信您的意思是从 permutate 而不是从 doIt 返回答案。 谢谢 nuway,我本来打算返回 doIt();该功能现在可以使用了。 【参考方案1】:

一般来说,我不是用断点跟踪这些,而是​​用打印语句。 当我输入一个函数时,我会打印函数名和参数值。当我离开时,我打印名称和退出(返回和状态)值。在这种情况下,我会在循环内做同样的事情。现在,让我们用更像英语的伪代码来看看这个

依次为每个数组元素: 从 arr 中删除该元素并将其附加到 item 如果我们清空了 arritem 记录为下一个排列 别的 在 arr 中少一个元素,在 item

中多一个元素重复出现
// When we reach this point, we've exhausted all the permutations that
//   begin with the original state of **item**.
// Back up one element
take the chosen element from **item** and re-append it to **arr**.
// Go to the next loop iteration, which will move to the next element.

当您完成此操作时,请记住您在运行时堆栈上对 doIt 进行了多次调用:第一次遍历 item[0] 的所有 3 个可能选择;第二个遍历 item[1] 的 2 个可能选择,第三个获取剩余元素,记录排列,然后备份到第二个调用。每个调用实例都维护其本地值 i、len、item

对于您的具体问题,当前三个调用将 [1, 2, 3] 标识为解决方案时,状态如下所示:

堆栈:

    doIt,i=0,len=1,item=3 doIt,i=0,len=2,item=2

    doIt,i=0,len=3,item=1

    arr=[3], temp=[1,2]

我们现在返回上一个调用,上面的#2,将调用#3 从堆栈中弹出。 在这个调用中,我们刚刚从 #3 doIt 调用返回。我们跳到 restore 点,将 2 拼接回 arr,然后迭代到循环的顶部。

i 增加到 1,从 arr 中选择值 3,留下 temp=[1,3] 和 arr=2。再次调用 doIt,另一个调用 #3 ...

...选择剩余的2,记录解[1,3,2],将2放回arr ,然后返回调用#2。

调用 #2 拼接 3 回到 arr,迭代,将 i 递增到 2,现在 n 已经用尽了它的 for 循环。它将 1 拼接回 arr 并返回调用 #1。

我们现在有了 temp=[]、arr=[1, 2, 3],并且只有我们原来的 doIt 调用在堆栈上。它前进到下一个循环迭代 (i=1),选择 2 for temp,我们从 2 开头的答案开始。

这足以让您了解这个想法吗?

【讨论】:

感谢您的周到、详细的回答。是的,这足以让我了解这个想法。我真的很感激! 很高兴能提供帮助。记住那些打印语句!我要做的另一件事是维护一个缩进全局变量,一串 2n 空格,其中 n 是嵌套级别。输入时,我附加两个空格;退出时,我把它们砍掉。每个 print 都以该字符串开头,因此它缩进了我所有的堆栈跟踪输出。

以上是关于这个递归数组置换函数如何在幕后工作?的主要内容,如果未能解决你的问题,请参考以下文章

指向 2D 数组的指针如何在幕后工作?

VBA IsMissing 函数如何在幕后工作?

Meteor 的反应在幕后是如何工作的?

pat1069 在离散数学中置换群思想上可用并查集和递归两种方法求解问题

React 钩子 useRef() 如何在幕后工作?那个参考到底是啥?

C ++如何在幕后工作[重复]