繁体   English   中英

如何在JavaScript中将自下而上的递归算法转换为迭代堆栈

[英]How to convert bottom-up recursive algorithm to iterative stack in JavaScript

给出以下算法:

 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)create(0) Klass 因此它首先创建叶节点,然后将其传递给父节点等。

想知道如何使用堆栈而不递归。 让我的头受伤:) 我理解如何使用堆栈从上到下创建,但不是自下而上。 对于自上而下,它基本上是这样的:

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])
      // ??
    }
  }
}

将任何递归代码机械转换为堆栈机器并非易事。 自动状态转换产生非常复杂的代码,只需考虑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; } 

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

假设我们总是在离开孩子之前计算出正确的孩子,我们需要一种方法来存储正确孩子的结果。 可以将它存储在原始堆栈上,但它会很复杂,因为该堆栈也用于计算左子。 所以我使用另一个堆栈来存储正确孩子的结果。

有三种状态:

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

当它看到状态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]; } 

这样的事情怎么样:

 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 } 

右侧节点首先在堆栈上推送,然后在左侧节点上推送,以便左侧节点在堆栈中更高并首先进行处理。

让我们先从只是i S:

 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); } 

有很多方法可以使用迭代生成的i s顺序; 从把它们全部推到一个阵列,然后跟踪任务向后; 使用i创建一个新的Klass并通过引用传递它,实质上将过程变为自上而下。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM