【问题标题】:How to convert bottom-up recursive algorithm to iterative stack in JavaScript如何在 JavaScript 中将自下而上的递归算法转换为迭代堆栈
【发布时间】:2018-06-27 02:17:13
【问题描述】:

给定以下算法:

console.log(JSON.stringify(create(0), null, 2))

function create(i) {
  if (i == 5) return
  return new Klass(i, create(i + 1), create(i + 1))
}

function Klass(i, l, r) {
  this.i = i
  this.l = l
  this.r = r
}

它在create(0) 中创建Klass last,在创建所有孩子之后,递归地。所以它首先创建叶节点,然后将其传递给父节点,等等。

想知道如何使用没有递归的堆栈来做到这一点。让我头疼:)。我了解如何使用堆栈从自上而下创建,而不是自下而上。对于自上而下,本质上是这样的:

var stack = [0]
while (stack.length) {
  var i = stack.pop()
  // do work
  stack.push(children)
}

自下而上,我看不出它应该如何工作。这就是我卡住的地方:

function create(i) {  
  var stack = []
  stack.push([i, 'open'])
  stack.push([i, 'close'])

  while (stack.length) {
    var node = stack.pop()
    if (node[1] == 'open') {
      stack.push([ node[0] + 1, 'open' ])
      stack.push([ node[0] + 1, 'close' ])
    } else {
      // ?? not sure how to get to this point
      var klass = new Klass(node[0], node[2], node[3])
      // ??
    }
  }
}

【问题讨论】:

  • 只是一个指针,循环和推送到同一个数组可能对健康有害!如果你反转最初的插入,它可能会变成一个永无止境的循环。

标签: javascript algorithm recursion iteration


【解决方案1】:

将任何递归代码机械地转换为堆栈机器并非易事。自动状态转换会产生非常复杂的代码,想想 C#-s 或 BabelJS-s 生成器。但可以肯定的是,它可以完成,但您将需要可变堆栈帧和/或寄存器。让我们看看我们面临的问题:

机器如何记住在哪里继续执行一个函数?

我们必须在堆栈本身上存储一些状态变量/指令指针。这就是您使用 "open""close" 标记模拟的内容。

函数的结果放在哪里?

有很多方法:

  • 将其存储在临时寄存器中
  • 向函数传递对字段的引用((对象,字段名)对),模拟out 参数
  • 使用像 @CtheSky 那样的第二个堆栈

使用可变堆栈帧和结果寄存器,转换后的代码如下所示:

console.log(JSON.stringify(create(0), null, 2))

function Klass(i, l, r) {
  this.i = i
  this.l = l
  this.r = r
}

function Frame(i) {
  this.ip = 0;
  this.i = i;
  this.left = null;
}

function create(i) {
  var result;
  var stack = [new Frame(i)];
  while (stack.length > 0) {
    var frame = stack[stack.length - 1];
    switch (frame.ip) {
      case 0:
        if (frame.i === 5) {
          result = undefined;
          stack.pop();
          break;
        }
        stack.push(new Frame(frame.i + 1));
        frame.ip = 1;
        break;
      case 1:
        frame.left = result;
        stack.push(new Frame(frame.i + 1));
        frame.ip = 2;
        break;
      case 2:
        result = new Klass(frame.i, frame.left, result);
        stack.pop();
        break;
    }
  }
  return result;
}

【讨论】:

  • 这更符合我的要求。谢谢。
  • 很好的实现。
【解决方案2】:

这是一个使用两个堆栈的解决方案。

假设我们总是在左孩子之前计算右孩子,我们需要一种方法来存储右孩子的结果。可以将其存储在原始堆栈中,但由于该堆栈也用于计算左孩子,因此会很复杂。所以我使用另一个堆栈来存储右孩子的结果。

有三种状态:

  • 需要工作 -> 需要将孩子推入堆栈进行计算
  • 需要合并 -> 等待左右子节点被计算
  • 完成工作 -> 所有工作都已完成

当它看到一个状态为finish work的节点时,它会检查下一个是否 节点状态为need merge:

  • 如果不是,当前完成的节点是右孩子,推到 缓存堆栈。并准备计算左孩子。
  • 如果是,当前完成的节点是左孩子,出栈 并缓存堆栈以获取根和右孩子,构造新节点并 将其推回堆栈,状态为finish work

console.log(JSON.stringify(create(2, 5), null, 2))

function Klass(i, l, r) {
  this.i = i;
  this.l = l;
  this.r = r;
}

function create(i, growto) {
    var stack = [];
    var cache = [];

    stack.push([i, 'need work']);
    while (stack.length && stack[0][1] !== 'finish work') {
        var cur = stack.pop();
        var val = cur[0];
        var status = cur[1];

        if (status === 'need work') {
            if (val !== growto) {
                stack.push([val, 'need merge']);
                stack.push([val + 1, 'need work']);
                stack.push([val + 1, 'need work']);
            } else {
                stack.push([val, 'finish work']);
            }
        } else if (status === 'finish work') {
            if (stack[stack.length - 1][1] !== 'need merge') {
                cache.push(cur);
            } else {
                var root = stack.pop()[0];
                var left = cur[0];
                var right = cache.pop()[0];
                stack.push([new Klass(root, left, right), 'finish work']);
            }
        }
    }

    return stack.pop()[0];
}

【讨论】:

  • 如果不赞成请发表评论,我想知道为什么
  • (如果你点击你的答案的票数,它会显示你的投票细分。目前没有downvotes。我的投票是第一个+1,然后我删除它,现在投票又起来了:)
【解决方案3】:

这样的事情怎么样:

console.log(JSON.stringify(create(4), null, 2))

function create(depth) {
    let n = Math.pow(2, depth);
    let nodes = [];
    for (let i = 0; i < n; i++)
        nodes.push(new Klass(depth));
    for (depth--; depth >= 0; depth--) {
        let next = [];
        while (nodes.length > 0)
            next.push(new Klass(depth, nodes.pop(), nodes.pop()));
        nodes = next;
    }
    return nodes[0];
}

function Klass(i, l, r) {
  this.i = i
  this.l = l
  this.r = r
}

获得相同结果的调用将是create(4);。并不是完全一样的创建顺序,是从下往上创建节点,而递归是这样的:

   7
 3   6
1 2 4 5

您也可以使用堆栈来模仿这种行为:

console.log(JSON.stringify(create(4), null, 2))

function create(depth) {
  let stack = [{depth: 0}]
  for (;;) {
    let i = stack.length - 1
    let cur = stack[i]
    if (typeof cur.left === 'undefined') {
      if (cur.depth < depth) {
        stack.push({depth: cur.depth + 1, parent: i, pos: 'right'})
        stack.push({depth: cur.depth + 1, parent: i, pos: 'left'})
      } else {
        stack[cur.parent][cur.pos] = new Klass(cur.depth)
        stack.pop()
      }
    } else {
      let node = new Klass(cur.depth, cur.left, cur.right)
      if (cur.depth == 0)
        return node
      stack[cur.parent][cur.pos] = node
      stack.pop()
    }
  }
}

function Klass(i, l, r) {
  this.i = i
  this.l = l
  this.r = r
}

右节点先入栈,再入左节点,这样左节点在栈中较高,优先处理。

【讨论】:

    【解决方案4】:

    让我们从is 开始:

    function create(i) {
      console.log(i)
    
      if (i == 3) return
    
      return new Klass(i, create(i+1), create(i+1))
    }
    
    function Klass(i, l, r) {
      this.i = i
      this.l = l
      this.r = r
    }
    
    console.log(JSON.stringify(create(0)))
    
    console.log('\nStack version:')
    
    let stack = [0];
    
    while (stack.length){
      let i = stack.pop();
    
      console.log(i);
    
      if (i < 3)
        stack.push(i + 1, i + 1);
    }

    有很多方法可以使用is 的迭代生成顺序;从将它们全部推入一个数组,然后向后跟踪分配;使用i 来创建一个新的Klass 并通过引用传递它,本质上将这个过程变成了自上而下的过程。

    【讨论】:

      猜你喜欢
      • 2018-12-02
      • 2016-06-29
      • 1970-01-01
      • 1970-01-01
      • 2017-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多