【问题标题】:Recursion in a loop?循环递归?
【发布时间】:2017-01-21 18:31:35
【问题描述】:

我一直试图理解Heap's Algorithm,几天来,我仍然无法将其包裹在我的脑海中,循环内的递归代码感觉有些不对劲。这是 Wikipedia 上 Heap 算法中递归版本的 Javascript 版本。

function permAlone(string) {

    var x = string.split(''); // Turns the input string into a letter array.

    var arr = x; // this is global, I did this so i can see elements being swapped on swap function

    function swap(a, b) { // This function will simply swap positions a and b   inside the input array.
        debugger;
        var le = arr[a]; // element a
        var lf = arr[b]; // element b

        var tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;

        var yt = arr; // only did this to see updated elements on the array
    }

    var permutations = []; //output
    function gen(length) {
        if (length === 1) { // if true, 

            var x = arr.join(''); 

            permutations.push(x); // push the updated joined' array

        } else {

            for (var i = 0; i < length; i++) { loop length = current elements
                debugger;
                gen(length - 1); // invoke recursion

                swap(length % 2 ? 0 : i, length - 1); //invoke swap function. length % 2 ? 0 : i <- ternary test for the lengths' call stack. length -1 for the last element
            }

        }
    }

    gen(arr.length);
    return permutations;
}


permAlone('abcd'); // this outputs to ["abcd", "bacd", "cabd", "acbd", "bcad", "cbad", "dbca", "bdca", "cdba", "dcba", "bcda", "cbda", "dacb", "adcb", "cdab", "dcab", "acdb", "cadb", "dabc", "adbc", "bdac", "dbac", "abdc", "badc"]

乍一看这段代码,我认为它是可以理解的,我理解它交换循环中的变量第 i 个值和最后一个元素,如果它是偶数,那么,如果它是奇数,它交换第一个和最后一个value.. 但是当我看到输出时,Recursion.. 不是重复太多了吗?谁能告诉我为什么?

【问题讨论】:

  • 代码是你自己实现的还是在wiki页面上?我在您的 OP 中的链接上找不到代码 sn-p
  • 你对算法背后的想法或递归有困难吗?
  • @shole,我提取了堆算法的部分,但它在这里link
  • @JoelLee 我得到了算法,但没有得到循环中的递归部分,为什么它会以重复的方式重复出现? :(
  • 好的,正在回答中。

标签: javascript algorithm loops recursion


【解决方案1】:

基本思想是,给定 n 个字符:

  1. 反复移动(交换)一个不同的字符到末尾
  2. 以该字符结尾,生成其他 n - 1 的所有排列

一般来说,为了让您的思想围绕递归,将递归调用视为调用某个其他函数来完成手头的任务会很有帮助。因此,不要递归调用 gen,而是“假装”你有其他一些函数,比如 gen2,你调用:

gen2(length-1);

gen2 视为一个黑盒子。你不关心它是怎么做的,但你知道如果它生成除最后一个字符之外的所有字符的所有排列,那么你知道gen 算法将产生正确的结果。

现在,如果你要写gen2,你会使用什么算法来做到这一点?好吧,你已经为gen 拥有相同的算法怎么样,除了现在gen2 需要有它自己的黑匣子,比如gen3。你如何实现gen3?再次使用相同的技巧,等等。

你会发现所有这些算法都是一样的,所以你可以回到你调用gen2gen算法,改为递归调用gen,然后扔掉所有冗余方法。

手动跟踪非平凡递归算法的执行可能会令人困惑,因为调用堆栈的深度总是在变化:增加,然后减少,然后再次增加,等等。我想这就是你在你的厘米。这是一个视觉表示,使用缩进来显示调用堆栈的深度。

gen(4)
   gen(3)  i = 0
      gen(2)  i = 0 
         gen(1)  i = 0
         gen(1)  i = 1
      gen(2)  i = 1 
         gen(1)  i = 0
         gen(1)  i = 1
      gen(2)  i = 2
         gen(1)  i = 0
         gen(1)  i = 1
   gen(3)  i = 1  
      gen(2)  i = 0
         gen(1)  i = 0
         gen(1)  i = 1
      gen(2)  i = 1
         gen(1)  i = 0
         gen(1)  i = 1
      gen(2)  i = 2    
         gen(1)  i = 0
         gen(1)  i = 1
  gen(3)  i = 2
      gen(2)  i = 0
         gen(1)  i = 0
         gen(1)  i = 1
      gen(2)  i = 1
         gen(1)  i = 0
         gen(1)  i = 1
      gen(2)  i = 2  
         gen(1)  i = 0
         gen(1)  i = 1          
  gen(3)  i = 3
      gen(2)  i = 0
         gen(1)  i = 0
         gen(1)  i = 1 
      gen(2)  i = 1
         gen(1)  i = 0
         gen(1)  i = 1
      gen(2)  i = 2  
         gen(1)  i = 0
         gen(1)  i = 1          

除了使用调试器来逐步执行之外,您可能会发现添加一些代码来打印上面显示的输出会很有帮助,方法是在gen 中添加第二个参数来表示堆栈深度:

gen(length, depth)

对于您的初次通话,请执行以下操作:

gen(arr.length, 0)

对于每个递归调用:

gen(length, depth+1)

我会留给你弄清楚如何根据深度值打印适当数量的空格。

【讨论】:

  • 谢谢!清除了我的困惑,我认为它在 -1 之后仍然循环,我什至想知道为什么在调用堆栈 1 上的推送之后它在堆栈 2s 的交换函数中。
【解决方案2】:

不确定您要问什么。该算法的解释已经在维基百科文章中给出。不确定是否有任何努力可以改善这一点。但如果问题是关于实现的,那么我认为它可以实现如下:

function swap(arr, a, b) {
  var tmp = arr[a];
  arr[a] = arr[b];
  arr[b] = tmp;

  return arr;
}

function generate(n, arr) {
  if (n === 1) {
    console.log(arr);
  } else {
    for (var i = 0; i < n - 1; i = i + 1) {
      generate(n - 1, arr);
      if (n % 2 === 0) {
        arr = swap(arr, i, n-1);
      } else {
        arr = swap(arr, 0, n-1);
      }
    }
    generate(n - 1, arr);
  }
}

let arr = ['a', 'b', 'c', 'd'];
arr = generate(4, arr);

http://jsbin.com/xigekoy/edit?js,console

【讨论】:

    【解决方案3】:

    并不过分。对于 n 个元素,排列的数量是 n!。在您的情况下,它是 4!,即 4*3*2*1 = 24。由于函数 gen 每次调用时都会将一个元素推入数组排列中,因此您知道它被调用了 24 次,因为有 24 次数组中的元素。

    注意n个元素的排列是

    第一个元素 + 其余元素 (n-1) 的所有排列加上

    第 2 个元素 + 其余元素 (n-1) 的所有排列加上

    ...

    第 n 个元素 + 其余元素的所有排列 (n-1)

    所以元素个数 p(n) = n * p(n-1)。查看这个 p(n),您可以了解为什么在循环中存在递归。

    【讨论】:

    • 正确分析调用次数。
    • 我还在Js中自我介绍,这是我第一次遇到这种代码,我想接受你的回答但我还是觉得有问题@JoelLee你能澄清一下吗
    猜你喜欢
    • 2018-06-27
    • 1970-01-01
    • 2013-08-12
    • 2011-02-09
    • 2023-04-02
    • 2011-08-11
    • 2015-01-26
    • 1970-01-01
    相关资源
    最近更新 更多