后序遍历的非递归实现依然依靠栈(Stack)实现,这里它的遍历顺序是「左-右-根」。类似于中序遍历要保证当前节点的左子树已无需遍历,对于后序遍历,我们输出一个节点之前,要保证它的左子树和右子树都已经无需遍历。
在之前的中序遍历,我们使用『curr』表示当前节点,并令其起到判断当前栈顶元素的左子树是否需要遍历的作用(即判断 curr 是否为null);而后序遍历我们还要保证右子树是否被遍历,因此我们这里再引入一个节点『right』表示「上次遍历的右节点」,帮助我们判断栈顶元素的右子树是否需要遍历。
参考代码
public class postorderTraversal { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> tempStack = new Stack<>(); TreeNode curr = root; TreeNode right = null; while (null != curr || !tempStack.empty()) { while (null != curr) { tempStack.push(curr); curr = curr.left; } // 这时可以保证栈顶元素的左子树为空或已被遍历了 curr = tempStack.peek(); // 遍历可能存在的右子树 // 如果栈顶元素的右节点存在且未被访问,则需要先对其右节点进行遍历 if (null != curr.right && curr.right != right) { curr = curr.right; right = null; continue; } // 此时,左、右子树均不存在或者被遍历过,则输出当前节点 result.add(curr.val); // 弹出遍历过的元素 tempStack.pop(); // curr可能是栈顶元素的右孩子 right = curr; // 将curr置为空保证下一次循环时直接取出栈顶元素(即这时curr的根元素) curr = null; } return result; } }便于记忆的模版代码
实现思路
我们知道后序遍历的顺序是「左-右-根」,再看一下之前前序遍历的顺序:「根-左-右」。在遍历时,我们将遍历结果顺序压入队列中,考虑如下两个操作:
前序遍历时,我们将结果压入队列尾部;如果我们现在将每次遍历的结果压入队列头部,那么我们从队列中得到的顺序就变成了「右-左-根」
在前序遍历时,为什么可以保证先访问左节点,再访问右节点?因为我们每次遍历时优先寻找当前节点的左孩子;如果我们现在每次遍历时,优先寻找右孩子,那么在『1』的基础上,队列中保存的顺序就变成了「左-右-根」,这就是后序遍历的顺序了。
在上述思路和前序遍历模版代码的基础上,我们可以知道代码的方式如下:
预处理:将辅助节点初始化为根节点,循环跳出条件依然是「当前节点curr为空」或「栈为空」
通过循环实现遍历
curr 存在时,将 curr 压入栈,将 curr 的值压入队列头部,并令 curr 指向其右孩子节点
如果 curr 为空,弹出栈顶元素,将 curr 指向被弹出栈顶节点的左孩子节点
模版代码
public class PostorderTraversal { public List<Integer> postorderTraversal(TreeNode root) { // 注意这里要指定为LinkedList(双向队列),不然无法使用addFirst()方法 LinkedList<Integer> result = new LinkedList<>(); Stack<TreeNode> tempStack = new Stack<>(); TreeNode curr = root; while (null != curr || !tempStack.empty()) { if (null != curr) { tempStack.push(curr); // 这里需要使用addFirst()将元素压入队列头部 result.addFirst(curr.val); curr = curr.right; } if (null == curr) { curr = tempStack.pop().left; } } return result; } }参考文章:
动画理解二叉树遍历
非递归实现二叉树前序遍历
非递归实现二叉树中序遍历