我试图理解使用2个堆栈实现后序遍历是如此直观。有人如何提出它,它只是一种观察或某种特定的思维方式,这有助于人们提出这样的方法。如果是,那么请解释如何思考正确的方向。
答案 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 - 例如,如果我们跟踪最后访问的节点。