如何在JavaScript

时间:2018-01-18 04:45:45

标签: javascript algorithm recursion iteration

给出以下算法:

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
}

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

想知道如何使用没有递归的堆栈来执行此操作。让我的头受伤:)我理解如何使用堆栈从上到下创建,但不是自下而上。自上而下,基本上是这样的:

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

4 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:2)

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

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

有三种状态:

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

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

  • 如果不是,则当前完成的节点是正确的子节点,将其推送到 缓存堆栈。并准备计算左孩子。
  • 如果是,则当前完成的节点是左子节点,从堆栈弹出 和缓存堆栈来获取root和right子,构造新节点和 将其推回到状态为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];
}




答案 2 :(得分: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
}
&#13;
&#13;
&#13;

获得相同结果的调用将是create(4);。它不是完全相同的创建顺序,它从下到上创建节点,而递归就像:

   7
 3   6
1 2 4 5

您还可以使用堆栈模仿此行为:

&#13;
&#13;
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
}
&#13;
&#13;
&#13;

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

答案 3 :(得分:0)

让我们从i s开始:

&#13;
&#13;
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);
}
&#13;
&#13;
&#13;

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