理解二叉树上迭代Postorder遍历实现中的逻辑

时间:2018-03-21 14:37:01

标签: data-structures tree binary-tree traversal tree-traversal

我试图理解使用2个堆栈实现后序遍历是如此直观。有人如何提出它,它只是一种观察或某种特定的思维方式,这有助于人们提出这样的方法。如果是,那么请解释如何思考正确的方向。

1 个答案:

答案 0 :(得分:3)

让我解释一下我是如何偶然发现解决方案的:

您从这个简单的观察开始:表面上看,预购和后序遍历仅在操作顺序上有所不同

preOrder(node):
    if(node == null){
         return
    }
    visit(node)
    preOrder(node.leftChild)
    preOrder(node.rightChild)

postOrder(node):
    if(node == null){
         return
    }
    postOrder(node.leftChild)
    postOrder(node.rightChild)
    visit node

preOrder函数进行一些计算(访问节点)和两次递归调用 - 让我们调用这个命令'VLR'。 postOrder函数进行两次递归调用,然后访问节点('LRV')。

preOrder适用于简单的迭代实现,我们访问节点,并将与递归调用相对应的计算推送到堆栈。

    var nodeStack = new Stack();
    nodeStack.push(this.root())
    while(!nodeStack.isEmpty()){
        var currentNode = nodeStack.pop();
        visit(currentNode);
        if (currentNode.rightChild){
            nodeStack.push(currentNode.rightChild)
        }

        if (currentNode.leftChild){
            nodeStack.push(currentNode.leftChild)
        }
    }

堆栈包含未访问节点的列表。我们弹出一个节点,访问它,推动它的右边孩子,推动左边的孩子。重复。这给了我们preOrder。

(注意:对于VLR,我们在左孩子之前推动正确的孩子,因为堆叠是LIFO - 我们访问在我们访问之前推送的孩子之前推送的孩子 - 我们想要在右边之前访问左孩子对于VRL,我们必须转发订单)

假设我们这样做,我们推右孩子,推左孩子,然后才访问节点(反映(1)递归调用的相对顺序和(2)postOrder中的节点访问的种类。我们尝试制作通过最后移动V来VLR到LRV。)

    var nodeStack = new Stack();
    nodeStack.push(this.root())
    while(!nodeStack.isEmpty()){
        var currentNode = nodeStack.pop();
        if (currentNode.rightChild){
            nodeStack.push(currentNode.rightChild)
        }

        if (currentNode.leftChild){
            nodeStack.push(currentNode.leftChild)
        }
        visit(currentNode);
    }   

这应该给我们postOrder,对吧?不,这还是preOrder。定义preOrder的原因不是我们在将子节点推送到堆栈之前访问了节点,而是我们在堆栈中永久删除节点(因此在访问它之后)弹出(并因此访问)堆栈中的子节点。堆栈中的孩子最终会在某个时刻弹出并访问,但他们的父母已经被弹出并访问过 - 因此这是preOrder。这种方法似乎是一个死胡同。

但还有另外一种方法!通过从前到后移动V而不是将VLR转换为LRV,我们可以通过颠倒VLR中的L和R的顺序(使其成为VRL)将'VLR'变为'LRV',然后将产生的'VRL'反转为把它变成'LRV'。

由左到右postOrder遍历(LRV)生成的序列与'VRL'的反向相同 - 由右到左preOrder遍历生成的序列。

让我们通过制作VRL调整我们的迭代preOrder:

    var nodeStack = new Stack();
    nodeStack.push(this.root())
    while(!nodeStack.isEmpty()){
        var currentNode = nodeStack.pop();

        if (currentNode.leftChild){
            nodeStack.push(currentNode.leftChild)
        }

        if (currentNode.rightChild){
            nodeStack.push(currentNode.rightChild)
        }

        visit(currentNode);
    }   

这将为我们提供一个VRL preOrder(反向为LRV的序列,postOrder)。 我们只需要反向遍历它! 这就是需要第二个堆栈的地方。如果我们将每个节点推送到另一个堆栈,然后只是从上到下遍历,我们将获得postOrder!

这是我的最终解决方案:

    var reverseStack = new Stack();
        while(!nodeStack.isEmpty()){
            var currentNode = nodeStack.pop()
            reverseStack.push(currentNode)
            if (currentNode.leftChild){
                nodeStack.push(currentNode.leftChild)
            }

            if (currentNode.rightChild){
                nodeStack.push(currentNode.rightChild)
            }
        }

        while(!reverseStack.isEmpty()){
            console.log(reverseStack.pop().getElement())
        }

您可以进行一些小的优化 - 这将为您提供在线查看的标准双栈解决方案。请注意,不需要两个堆栈 - 并且可以仅使用一个堆栈实现postOrder - 例如,如果我们跟踪最后访问的节点。