书接上回,因为后序遍历是非尾调用的递归,用一个栈实现遍历比较复杂。
下面我们就来讲,使用一个栈的后序遍历怎么实现。
思路一:
我们可以采用和先序,中序遍历相同的思路压栈。
但是当我们经过某节点,走向其右子树时,不使用该节点,所以我们应该把该节点再次压栈。
我们访问某节点的条件是:该节点没有孩子,或者右边孩子已经被访问过。
请注意这样一个事实:当我们后序遍历某个结点时,我们上一个访问的元素必然是其右子节点,所以我们可以用pre
来记录上一个访问的元素,进行条件判断。
代码如下:
public void traverse(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
TreeNode pre = null;
while(node != null){
stack.push(node);
node = node.left;
}
while(!stack.empty()){
node = stack.pop();
if(node.right != null && node.right != pre){
stack.push(node);
node = node.right;
while(node != null){
stack.push(node);
node = node.left;
}
}else{
//visit 后序 pre = node;
}
}
}
这种遍历,有一个好玩的地方,就是:在代码运行到//visit 后序
那里时,栈中的元素,刚好是node的所有祖先 。
这是因为:① 当我们遍历某节点时,其“右上方”的任何节点,还没有被经过(pass)。
这其实是‘先序’的入栈方式决定的。
② 当我们遍历右节点时,其左节点必然已经出栈。
这是由后序遍历的出栈方式决定的。
另一种写法:
public void traverse(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
TreeNode pre = null;
while(node != null || !stack.empty()){
if(node != null){
stack.push(node);
node = node.left;
}else{
node = stack.pop();
if(node.right != null && node.right != pre){
stack.push(node);
node = node.left;
}else{
//visit 后序 pre = node;
node = null;
}
}
}
}
思路二:
思路一是采用回溯的思想,利用pre
节点,把每个父节点入栈(pass)了两次。
那么如果我们从根节点遍历的时候,把右子节点A在其之前入栈,出栈时,通过判断栈顶元素是否为右节点A,就能根据这个条件进入右子树。
代码如下:
public void traverse(TreeNode root){
if (root == null) return;
TreeNode cur, pre = null;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.empty()) {
cur = stack.peek();
if (pre == null || pre.left == cur ||
pre.right == cur) {
if (cur.left != null)
stack.push(cur.left);
else if (cur.right != null)
stack.push(cur.right);
else {
stack.pop();
//visit 后序 }
} else if (cur.left == pre) {
if (cur.right != null)
stack.push(cur.right);
else {
stack.pop();
//visit 后序 }
} else if (cur.right == pre) {
stack.pop();
//visit 后序 }
pre = cur;
}
}
}
唉,人这一辈子啊,总要有一些看不懂的代码。55555
下一篇,我们看一下Morris遍历,一种不用栈也不用递归的遍历方式~ 还有BFS(广度优先搜索),也就是层次遍历。