相信大家对二叉树的前中后序遍历的递归方法非常熟悉,其实就是一个类似的框架
if(p!=null){
//前序遍历时访问结点
if(p.left!=null){
//递归进入左子节点遍历
}
//中序遍历时访问结点
if(p.right!=null){
//递归进入右子节点遍历
}
//后序遍历时访问结点
}
可以看到,写前中后序遍历的递归方法非常简单,但是这种方式对我们学习编程的话意义并不是很大,而是用迭代方法可以体现出一个人的思维能力,尤其是在面试中,那么如何写出二叉树的前中后序遍历的非递归方法的代码呢?
二叉树的前中后序遍历框架
前中后序遍历也有它们相应的框架:它们的核心代码如下:(来自leetcode郭郭)
前序核心代码
while(root != null || !stack.isEmpty()){
//go left down to the ground
while(root != null){
res.add(root.val);
stack.push(root);
root = root.left;
}
//if we reach to the leaf, go back to the parent right, and repeat the go left down.
TreeNode cur = stack.pop();
root = cur.right;
}
中序核心代码
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
res.add(root.val);
root = root.right;
}
后序核心代码
while(root != null || !stack.isEmpty()){
while(root != null){
res.add(root.val);
stack.push(root);
root = root.right;
}
TreeNode cur = stack.pop();
root = cur.left;
}
二叉树的前序遍历
对应leetcode144题
问题描述
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,2]
输出:[1,2]
解题思路:
前序遍历的顺序是根左右
前序遍历的框架如下:
while(root != null || !stack.isEmpty()){
//go left down to the ground
while(root != null){
res.add(root.val);
stack.push(root);
root = root.left;
}
//if we reach to the leaf, go back to the parent right, and repeat the go left down.
TreeNode cur = stack.pop();
root = cur.right;
}
对于前序遍历,首先是拿到根节点,然后一直往左走,而在框架中,我们首先访问相应的结点值,然后将该节点入栈,并一直往左走,即指向其右子节点。当while循环结束后,即发现左为空,然后就从栈中弹出一个元素,往其右子节点走,由于最开始3的右子节点为空,那么可以不管,接着下一趟外层循环的时候,由于root为空,不走内层while循环,就从栈中弹出元素2,然后访问2的右子节点。进入到了4,然后再一直往4的左边走,我们可以看到,前序遍历的过程中,总是尽可能的往左下走,当走到左下,已经无路可走的时候,我们就弹出元素,并走到当前节点的右边,然后又一直往左下走。也就是说,这里是有一个趋势的,也就是我一直往左下走,直到走到无路可走,这时候我再弹栈,看看右边是否有要走的路,如果右边还有,那么我们就到右边,然后再立刻往左下走。
实现代码
class Solution {
List<Integer> list; //用list集合来保存我们遍历过程中的元素
public List<Integer> preorderTraversal(TreeNode root) {
list=new ArrayList<>();
//创建一个栈,模拟递归时的系统栈
Stack<TreeNode> stack=new Stack<TreeNode>();
//当结点不为空并且栈也不为空的时候,就说明还有元素待遍历
while(root!=null || !stack.empty()){
//如果可以一直往左走,就一直往左右,一直走到底
while(root!=null){
list.add(root.val); //前序遍历先访问根节点
stack.push(root); //将结点入栈
root=root.left; //一直往走左
}
//如果不能再往左走,就弹出一个元素
root=stack.pop(); //弹出栈顶元素
//然后先往右走一步
root=root.right;
}
return list;
}
}
二叉树的后序遍历
问题描述
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解题思路
后序遍历的顺序是左右根
后序遍历的序列是左右根,如果我们将其逆序,就变成了根右左,这与前序遍历有着异曲同工之妙,如果我们能拿到根右左,这个时候我们就可以通过reverse的方式拿到左右根,那根右左与根左右有什么区别呢,在前序遍历中我们是root=root.left,那么在这里,我们就是root=root,right,在前序遍历中我们是root=root.right,那么在这里,我们就是root=root,left,其它代码其实是惊人的相似。如果从图上来说,我们是尽可能的往右走,直到走投无路的时候,我们才会弹出元素,并且考虑这个元素的左节点。但是一旦拿到新节点以后,我们又会执着的往右走,直到走到无路可走,再往上走
最后,我们可以使用Java集合类提供的reverse函数进行逆序。
实现代码
class Solution {
private List<Integer> list;
public List<Integer> postorderTraversal(TreeNode root) {
list=new ArrayList<Integer>();
Stack<TreeNode> stack=new Stack<TreeNode>();
//后序遍历序列是左右根,我们遵循根右左的规则,它与前序遍历左右刚好相反
while(root!=null || !stack.empty()){
//我们选择一直往右走,直到走到底
while(root!=null){
list.add(root.val); //根右左,同样是先访问根节点
stack.push(root); //入栈
root=root.right; //一直往右走
}
root=stack.pop(); //弹出一个元素
//走到底之后,就向左走一步
root=root.left;
}
//上面我们得到了一个根右左的序列,要想得到左右根,转置一下即可。
Collections.reverse(list);
return list;
}
}
二叉树的中序遍历
问题描述
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,null,2]
输出:[1,2]
解题思路:
中序遍历的序列为左根右
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
res.add(root.val);
root = root.right;
}
在前序遍历和后序遍历时,我们都是在内层while循环遍历的过程中访问结点(即将结点值添加到容器中),但是中序遍历在压栈的时候并没有把值放入。因为我们顺序是左根右,直到我们走到无路可走的时候,我们弹出的第一个元素放入到结果中,因为前序遍历和逆后序遍历都是先访问根节点,所以在内存while循环遍历的时候就已经访问了。这里的过程是,当我们一直往左走的时候,一直走到3这个重点,然后弹出,把3添加到结果集中,然后继续弹出,直到存在右节点的时候,就向右走一步,然后再继续之前的过程。
实现代码
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<Integer>();
Stack<TreeNode> stack=new Stack<TreeNode>(); //辅助栈
while(root!=null || !stack.empty()){
//一直往左走,直到走到底,但是我们不将中间拿到的结点值放入到容器中
while(root!=null){
stack.push(root);
root=root.left;
}
//出栈一个结点,并将其放入到容器中
root=stack.pop();
list.add(root.val);
root=root.right;
}
return list;
}
}
总结:
对于前中后序遍历,他们的非递归遍历代码是极其相似的,我们返回的是一个list集合,这里一个重要的点是到底我们何时把拿到的结点值添加到容器中,这是我们需要重点考虑的。