这个递归数组置换函数如何在幕后工作?
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 如果我们清空了 arr 将 item 记录为下一个排列 别的 在 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 都以该字符串开头,因此它缩进了我所有的堆栈跟踪输出。以上是关于这个递归数组置换函数如何在幕后工作?的主要内容,如果未能解决你的问题,请参考以下文章
pat1069 在离散数学中置换群思想上可用并查集和递归两种方法求解问题