如何提出没有堆栈的迭代解决方案
实现迭代树遍历不需要堆栈!您可以通过在树节点数据结构中保留父指针来摆脱任何堆栈。这就是你想出的方法:
什么是迭代解决方案?迭代解决方案是在循环中重复执行代码的固定部分的解决方案(几乎是迭代的确定性)。循环的输入是系统的状态 s1,输出是状态 s2,循环将系统从状态 s1 带到状态 s2。您从初始状态 s 开始,并在达到最终所需状态 s 时结束。
所以我们的问题归结为寻找:
- 有助于我们实现这一目标的系统状态特征。初始状态将与我们的初始条件一致,最终状态将与我们想要的结果一致
- 查找作为循环的一部分重复执行的指令
(这实际上将树变成了状态机。)
在树遍历中,每一步都会访问一个节点。树的每个节点最多被访问三次——一次来自父节点,一次来自左子节点,一次来自右子节点。我们在特定步骤对节点做什么取决于它是三种情况中的哪一种。
因此,如果我们捕获所有这些信息:我们正在访问的是哪个节点,以及它是哪种情况,我们就有了系统的特征。
捕获此信息的一种方法是存储对先前节点/状态的引用:
Node current;
Node previous;
如果previous = current.parent,那么我们是从父访问。如果previous = current.leftChild,我们从左边访问,如果previous = current.rightChiild,我们从右边访问。
我们可以获取此信息的另一种方式:
Node current;
boolean visitedLeft;
boolean visitedRight;
如果visitedLeft和visitedRight都为false,那么我们是从父节点访问,如果visitedLeft是true但是visitedRight是false,我们是从左边访问,如果visitedLeft和visitedRight都为true,我们是从右边访问(第四种状态:visitedLeft false 但visitedRight false,preOrder 中永远不会到达)。
最初,我们从 viisitedLeft = false、visitedRight = false 和 current = root 开始。当遍历完成时,我们期望visitedLeft = true,visitedRight = true,并且current = null。
在作为循环的一部分重复运行的指令中,系统必须从一种状态转移到另一种状态。所以在指令中,我们只是告诉系统当它遇到任何状态时该做什么,以及何时结束执行。
您可以将所有三个遍历组合到一个函数中:
void traversal(String typeOfTraversal){
boolean visitedLeft = false;
boolean visitedRight = false;
TreeNode currentNode = this.root;
while(true){
if (visitedLeft == false && currentNode.leftChild != null){
if(typeOfTraversal == "preOrder"){
System.out.println(currentNode.key);
}
currentNode = currentNode.leftChild;
continue;
}
if (visitedLeft == false && currentNode.leftChild == null){
if(typeOfTraversal == "preOrder"){
System.out.println(currentNode.key);
}
visitedLeft = true;
continue;
}
if (visitedLeft == true && visitedRight == false && currentNode.rightChild != null){
if(typeOfTraversal == "inOrder"){
System.out.println(currentNode.key);
}
currentNode = currentNode.rightChild;
visitedLeft = false;
continue;
}
if (visitedLeft == true && visitedRight == false && currentNode.rightChild == null){
if(typeOfTraversal == "inOrder"){
System.out.println(currentNode.key);
}
visitedRight = true;
continue;
}
if (visitedLeft == true && visitedRight == true && currentNode.parent != null){
if(typeOfTraversal == "postOrder"){
System.out.println(currentNode.key);
}
if (currentNode == currentNode.parent.leftChild){
visitedRight = false;
}
currentNode = currentNode.parent;
}
if (visitedLeft == true && visitedRight == true && currentNode.parent == null){
if(typeOfTraversal == "postOrder"){
System.out.println(currentNode.key);
}
break; //Traversal is complete.
}
如果给你节点级锁,这个算法允许并发遍历和更新树。除了分离非叶节点之外的任何原子操作都是安全的。
如何提出基于堆栈的解决方案
在考虑将递归解决方案转换为迭代解决方案或为递归定义的问题提出迭代解决方案时,堆栈是有用的数据结构。调用堆栈是一种堆栈数据结构,用于存储有关计算机程序的活动子例程的信息,是大多数高级编程语言实现底层递归的方式。因此,在迭代解决方案中显式使用堆栈,我们只是在模仿处理器在编写递归代码时所做的事情。 Matt Timmermans 的回答很好地说明了为什么要使用堆栈以及如何提出基于堆栈的显式解决方案。
我已经在此处写过如何提出带有两个堆栈的 postOrder 解决方案:Understanding the logic in iterative Postorder traversal implementation on a Binary tree。
基于父指针的方法比基于堆栈的方法消耗更多的内存。在堆栈上,指向仍要处理的节点的指针是瞬态的,并且只需要 O(log n) 堆栈空间的顺序,因为您只需要为沿树的单个路径保留足够多的指针(实际上是,这可能会更少)。相比之下,将父指针与节点一起存储需要固定的 O(n) 空间。