本文主要介绍二叉树前序中序后序遍历的非递归写法
在探讨如何写出二叉树的前序中序后序遍历代码之前,我们先来明确一个问题,前序中序后序遍历根据什么区分?
二叉树的前序中序后序遍历,是相较根节点说的。最先遍历根节点即为前序遍历,第二遍历根节点即为中序遍历,最后遍历根节点为后序遍历。左右节点的遍历顺序都是先左后右。
明确了上述之后,我们再来看一下如何定义树节点这一数据结构,新建树节点类,其具有值,左子树指针,右子树指针三种属性,加上构造方法后定义出来的结构如下:
// 树结构
public class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(){
}
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
明确了上述之后,让我们来一起看下二叉树前序遍历、中序遍历、后序遍历的非递归写法吧
1. 前序遍历
我们都知道递归底层是用栈来操作,那我们的非递归写法也就是使用栈模拟出二叉树遍历的操作。前序遍历使用栈模拟的操作过程如下:
每次执行的操作为:根节点出栈,节点值加入结果数组,然后将根节点右子节点入栈,再将根节点左子节点入栈。循环往复,直至栈为空。
先操作根节点值我们大家都能理解,那为什么是右子节点先入栈,然后左子节点后入栈那?
栈的操作为先进后出,因此先入栈的右子节点后出栈,后入栈的左子节点先出栈,这样得到的遍历顺序就为 —— 中 、左、右,因此要先入栈右子节点,后入栈左子节点。
代码:
// 二叉树先序遍历迭代写法
public List<Integer> preorderTraversal(TreeNode root){
// 存储遍历结果
List<Integer> ansList = new ArrayList<>();
if(root == null){ // 树为空情况处理
return ansList;
}
// 用于模拟操作的栈
Stack<TreeNode> stack = new Stack<>();
// 初始化放入根节点
stack.push(root);
while(!stack.isEmpty()){
// 先序遍历先处理根节点
TreeNode node = stack.pop();
ansList.add(node.val);
// 加入右节点 —— 先加入右节点后加入左节点 —— 从而保证出栈顺序为先左再右
if(node.right != null) stack.push(node.right);
// 加入左节点
if(node.left != null) stack.push(node.left);
}
return ansList;
}
2. 后序遍历
如果你理解了二叉前序遍历的非递归写法,那么后序遍历的非递归写法,就可以很快写出了。我们来思考一下前序遍历和后序遍历的关系。前序遍历的遍历顺序为 中左右,我们将其结果翻转一下就变成了 右左中,而后序遍历的顺序为 左右中,因此只需要把前序遍历的代码稍微调整一下左右子节点入栈的顺序,最后再把结果数组翻转就能得到后序遍历的写法了。具体操作为左节点先入栈,右节点后入栈(与我们在前序遍历中的入栈顺序正好相反),最后再使用 Collections.reverse()
API 将结果List 翻转。
代码:
// 后序遍历迭代写法 —— 思考其和前序遍历的不同 —— 前序遍历是 中左右 , 将其变为 中右左 ,再翻转最后的结果数组就变为 左右中 ,刚刚好是后序遍历的结果
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ansList = new ArrayList<>();
if(root == null){
return ansList;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){ // 出栈顺序为 中右左
TreeNode node = stack.pop();
ansList.add(node.val);
if(node.left != null) stack.push(node.left);
if(node.right != null) stack.push(node.right);
}
Collections.reverse(ansList); // 翻转链表
return ansList;
}
3. 中序遍历
前序遍历之所以看起来简单,是因为其操作的节点和其遍历顺序相同。而对中序遍历我们的模拟方法为:先一直沿左子树遍历,将沿途的节点入栈,直到遍历到最左叶子节点,这时就可以开始我们的操作了,操作方法为将最左叶子节点出栈进行操作,然后将右子节点入栈,右子节点重复执行上述过程(即找到右子节点的最左子节点…省略重复过程…)。
具体到代码: 因为不是最先遍历root 节点了,因此不需要先在 stack 中加入 root。而是使用一 cur 节点记录当前遍历的节点,开始的时候 cur 一直向左找,直到找到第一个要操作的节点,cur 和 stack 其中一个不为空就继续遍历。
代码:
// 二叉树中序遍历迭代写法
public List<Integer> inorderTraversal(TreeNode root) {
// 存储遍历结果
List<Integer> ansList = new ArrayList<>();
if(root == null){ // 树为空情况处理
return ansList;
}
// 用于模拟操作的栈
Stack<TreeNode> stack = new Stack<>();
/********** 上述内容所有遍历都一样 ************/
TreeNode cur = root;
while(cur!=null || !stack.isEmpty()){
if(cur != null){ // 一直放左子节点,直到找到要处理的第一个左子节点
stack.push(cur);
cur = cur.left; // 将当前遍历节点变为左子节点
} else{ // 到达要处理的位置
TreeNode node = stack.pop();
ansList.add(node.val); // 处理根节点 —— 先处理根节点,然后就加入右叶子节点
cur = node.right; // 将当前遍历节点改为右子节点
}
}
return ansList;
}