堆的算法演练

Heap's Algorithm Walk-Through

因此,我正在学习递归,并且正在应对一项编码挑战,该挑战需要数组中元素的所有变体。

我被指向 Heap's algorithm,这就是我目前在 JavaScript 中制定的内容。

function generate(n, array) {
    if (n == 1) {
        return array;
    }
    else {
        for (var i = 0; i < n - 1; i++) {
            generate(n - 1, array);
            if (n % 2 == 0) {
                swap(array[i], array[n - 1]);
            }
            else {
                swap(array[0], array[n - 1]);
            }
        }
        generate(n - 1, array);
    }
}

请忽略我没有将 "swap" 个实例翻译成 JavaScript 的事实。

我有点不确定,因为 如何准确地遍历该算法的递归部分

假设我有数组:[A,B,C,D,E]。我相信我将传递给外部函数的参数是:

generate(5, [A,B,C,D,E]);

在这种情况下,n 不等于 1,因此我将转到 "else" 部分。 我遇到了 for 循环,因为 n 是 5,所以它执行了。

接下来:函数调用自身,但这次的参数是:

generate(4, [A,B,C,D,E]);

这就是我开始困惑的地方:

当我完成这个过程时,我是否会转到 "if"/"else" 条件 还是我(在递归调用之后)返回到外部函数的开头?

更新

下面是完全翻译的 JavaScript 版本的 Heap 算法。

function generate(n, array) {
//console.log("ENTER", n, array)

if (n == 1) {

    console.log(array);
}

else {

    for (var i = 0; i < n - 1; i++) {
        //console.log("RECUR", n-1, array)


        generate(n - 1, array);

        if (n % 2 == 0) {
            //console.log("SWAP ", n, array)

            var one = array[i];
            var two = array[n - 1];

            array[i] = two;

            array[n - 1] = one;

            //swap(array[i], array[n - 1]);
        }

        else {
            //console.log("SWAP ", 0, n-1)

            var first = array[0];
            var second = array[n - 1];

            array[0] = second;

            array[n - 1] = first;


            //swap(array[0], array[n - 1]);
        }
         //console.log("array", array)
    }

    //console.log("RECUR 2", n-1, array)

    generate(n - 1, array);
}

//console.log("LEAVE", n, array)

}

我在纸上遍历它,直到我得到一个重复的点,再次 [A,B,C,D]。

以为我搞砸了,我决定实施 Prune 关于控制台输出的建议,以查看控制台中发生的情况,这很有帮助。

修复了一个小错误后,现在可以正常工作了。

感谢所有帮助过的人!

此时我的规范回答是,如果您在纸上遍历算法时遇到困难,不要!让电脑为你做。插入一堆console.log命令来跟踪执行。跟踪每个例程和调用的进入和退出,包括有用的值。

function generate(n, array) {
    console.log("ENTER", n, array)
    if (n == 1) {
        return array;
    }
    else {
        for (var i = 0; i < n - 1; i++) {
            console.log("RECUR", n-1, array)
            generate(n - 1, array);
            if (n % 2 !== 0) {
                console.log("SWAP ", n, array)
                swap(array[i], array[n - 1]);
            }
            else {
                console.log("SWAP ", 0, n-1)
                swap(array[0], array[n - 1]);
            }
            console.log("array", array)
        }
        console.log("RECUR 2", n-1, array)
        generate(n - 1, array);
    }
    console.log("LEAVE", n, array)
}

这会给你一个可爱的执行轨迹。为了提高可读性,将每行输出缩进 2*(5-n) 个空格。

让我列举一下你的台词,这样我们就可以更容易地参考它们(见下文)

你从 generate(5, [A,B,C,D,E]); 走到了 generate(4, [A,B,C,D,E]);(在第 7 行),然后你第二次进入 generate,没有完成你的第一次调用,所以你暂停到第一个电话并开始与第二个电话一起走路

现在(第二次调用)n=4 所以你再次到达第 7 行,但这次第三次调用 generate(3, [A,B,C,D,E]) 开始(没有完成之前的调用)

第 4 次调用也是如此 generate(2, ...),但第 5 次调用 generate(1, ...) 情况开始发生变化

在第 5 次调用 n=1 中,因此第 2 行的条件为真并返回 array,我们回到我们来自的地方,即第 7 行的第 4 次调用,其中返回的数组什么都不做(它没有分配或任何东西)然后我们在第 4 次调用时到达第 8 行(第一次),n=2 因此第二个 swap 发生并且 i 递增,回到第 7 行,我们再次调用 generate(1, ...) 以获得相同的结果......等等

01. function generate(n, array) {
02.     if (n == 1) {
03.         return array;
04.     }
05.     else {
06.         for (var i = 0; i < n - 1; i++) {
07.             generate(n - 1, array);
08.             if (n % 2 !== 0) {
09.                 swap(array[i], array[n - 1]);
10.             }
11.            else {
12.                swap(array[0], array[n - 1]);
13.            }
14.        }
15.        generate(n - 1, array);
16.    }
17. }