力扣刷题记录-二叉树的前中后序遍历模板(统一模板,超级简单好记!!!)

这篇主要介绍二叉树遍历的递归方法和迭代方法。最后进行n叉树的遍历练习。

二叉树前中后序遍历分别对应力扣144、94、145题,n叉树遍历对应力扣589、590题。

递归方法

//前序遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new LinkedList<Integer>();//用res来存遍历结果
        preOrder(res,root);
        return res;
    }
    public void preOrder(List<Integer> res,TreeNode root){
        if(root==null)return;//递归终止条件,遍历到空节点就返回上一层
        res.add(root.val);//访问结点(这句位置决定递归顺序)
        preOrder(res,root.left);
        preOrder(res,root.right);
    }
}

//中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new LinkedList<>();
        inOrder(res,root);
        return res;
    }
    public void inOrder(List<Integer> res,TreeNode root){
        if(root==null)return;
        inOrder(res,root.left);
        res.add(root.val);
        inOrder(res,root.right); 
    }
}

//后序遍历
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res=new LinkedList<>();
        postOrder(res,root);
        return res;
    }
    public void postOrder(List<Integer> res,TreeNode root){
        if(root==null)return;
        postOrder(res,root.left);
        postOrder(res,root.right);
        res.add(root.val);
    }
}

迭代方法(未统一写法)

因为递归调用会把函数的局部变量、参数值和返回的地址入栈,return的时候从栈顶弹出上次的各项参数,所以递归可以返回上一层位置(实质是用栈记录下了上一层的信息)。所以三种遍历方式可以用栈来模拟递归的过程,也就是所谓的迭代(递归是一环套一环的执行,而迭代是按照一定的逻辑顺序向前推进)。

·前序迭代

前序遍历的顺序是根->左->右,需要先处理根结点(根节点先入栈,在后续每一次处理中,都将栈顶元素出栈,加入res中),然后将右孩子入栈,再将左孩子入栈,这样可以保证出栈的顺序是左孩子在右孩子前;

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res= new LinkedList<>();
        Stack<TreeNode> stack=new Stack<>();//栈里面存的是TreeNode类型的结点
        if(root!=null)stack.push(root);//如果空树,什么都不管,栈就是空的,最后直接返回空的res
        //前序遍历顺序(根左右),用栈模拟要根先入栈再出栈,然后右结点入栈,左节点入栈,这样出栈顺序就符合
        while(!stack.isEmpty()){
            root=stack.pop();
            res.add(root.val);
            if(root.right!=null)stack.push(root.right);
            if(root.left!=null)stack.push(root.left);
        }
        return res;
    }
}

·中序迭代

做完了前序迭代,肯定是想稍微改改前序迭代代码就套上中序,但这样是不行的。根源就在于对根结点的访问和处理上,前序的顺序是中左右,遍历过程中最先访问(遍历)的是根结点,每次循环中最先处理(出栈加入res)的也都是中间节点,然后是孩子结点。也就是说对结点访问和处理是连在一起的
但是中序遍历的顺序是左中右,要先访问根节点,然后一直向左子树访问,直到底部,开始处理结点(出栈加入res中),也就是说对结点的访问和处理是分开进行的,导致了处理和访问顺序的不一致。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res =new LinkedList<>();
        if(root==null)return res;
        Stack<TreeNode> stack =new Stack<>();
        TreeNode cur=root;//用cur来遍历树
        //当栈或当前遍历结点非空的时候
        while(!stack.isEmpty()||cur!=null){
            if(cur!=null){//当前遍历结点非空,就要入栈,然后往左子树遍历
                stack.push(cur);
                cur=cur.left;//左
            }else{//当前遍历结点为空
                cur=stack.peek();//栈顶元素就是要处理的数据,用cur保存
                stack.pop();
                res.add(cur.val);//中
                cur=cur.right;//再往右子树遍历
            }
        }
        return res;
    }
}

·后序迭代

前序迭代顺序是中左右,后序是左右中,其实只要在前序基础上,将顺序变为中右左,再将最后的res翻转,就可以得到左右中的顺序;

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res =new LinkedList<>();
        if(root==null)return res;
        Stack<TreeNode> stack=new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            root=stack.pop();
            res.add(root.val);
            if(root.left!=null)stack.push(root.left);
            if(root.right!=null)stack.push(root.right);
        }
        Collections.reverse(res);
        return res;
    }
}

迭代方法(统一模板)

在前一组迭代写法中,中序遍历是因为需要先访问到最左下才可以开始处理结点,而前序是可以一边访问一边处理,这样导致了代码写法的不一致,为了统一这个风格,可以在遍历的过程中,都将结点入栈,只不过在要处理的节点(也就是要加入res的结点)入栈后再入栈一个null结点作为标记,这样在后续出栈中如果碰到null结点,就知道下一个出栈的结点是要加入res的,这样一来只要调整左中右结点的入栈顺序,就可以控制最后出栈的顺序;说得有些绕,直接看三种遍历的对比代码找感觉:

//前序遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res=new ArrayList<>();
        Deque<TreeNode> stack=new LinkedList<>();
        if(root!=null)stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur=stack.peek();//cur获取栈顶元素
            if(cur!=null){//栈顶元素非空,出栈
                stack.pop();//避免后续对栈顶的元素重复操作,统一先出栈(前面已经用cur记录了,不怕丢)
                if(cur.right!=null)stack.push(cur.right);//若左右子树为空,则空节点不入栈,保证栈顶元素为空节点只发生在是中结点入栈后
                if(cur.left!=null)stack.push(cur.left);
                //因为是前序遍历(中左右),入栈要按右左中的顺序
                stack.push(cur);
                stack.push(null);//在中结点入栈后,null入栈作为标记;
            }else{//若栈顶元素为空,说明下一个栈顶元素是“中”结点
                stack.pop();//null出栈
                cur=stack.peek();//null的下一个栈顶元素就是“中结点”,用cur暂存
                stack.pop();//存下后就可出栈
                res.add(cur.val);
            }
        }
        return res;
    }
}

//中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res= new ArrayList<>();
        Deque<TreeNode> stack=new LinkedList<>();
        if(root!=null)stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur=stack.peek();
            if(cur!=null){
                stack.pop();
                if(cur.right!=null)stack.push(cur.right);
                stack.push(cur);
                stack.push(null);
                if(cur.left!=null)stack.push(cur.left);
            }
            else{
                stack.pop();
                cur=stack.peek();
                stack.pop();
                res.add(cur.val);
            }
        }
        return res;
    }
}

//后序遍历
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res=new ArrayList<>();
        Deque<TreeNode> stack= new LinkedList<>();
        if(root!=null)stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur=stack.peek();
            if(cur!=null){
                stack.pop();
                stack.push(cur);
                stack.push(null);
                if(cur.right!=null)stack.push(cur.right);
                if(cur.left!=null)stack.push(cur.left);
            }else{
                stack.pop();
                cur=stack.peek();
                stack.pop();
                res.add(cur.val);
            }
        }
        return res;

    }

可以观察到这样设置的话,代码的区别就只在于

//1
stack.push(cur);
stack.push(null);

以及

//2
if(cur.right!=null)stack.push(cur.right);
//3
if(cur.left!=null)stack.push(cur.left);

这三句的顺序的不同。


二叉树遍历

LeetCode 144. 二叉树的前序遍历

原题链接

2023.06.11 二刷

递归方法代码如下:

//递归版本
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        preOrder(res,root);
        return res;
    }
    public void preOrder(List<Integer> res,TreeNode root){
        if(root==null)return;
        res.add(root.val);
        preOrder(res,root.left);
        preOrder(res,root.right);
    }
}

迭代方法(非统一模板),代码如下:

//迭代版本
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res= new ArrayList<>();
        Deque<TreeNode> stack=new LinkedList<>();
        if(root!=null)stack.push(root);
        //前序遍历顺序(根左右),用栈模拟要根先入栈再出栈,然后右结点入栈,左节点入栈,这样出栈顺序就符合
        while(!stack.isEmpty()){
            root=stack.pop();
            res.add(root.val);
            if(root.right!=null)stack.push(root.right);
            if(root.left!=null)stack.push(root.left);
        }
        return res;
    }
}

迭代法,统一模板:

思想:

  迭代法需要使用栈,但用栈的话就无法解决遍历结点和处理结点的一致问题。
  那么将访问到的结点放入栈中,处理过的结点也放进去,但是要对它进行标记,即处理的结点放入栈中之后,紧接着放入一个空指针入栈作为标记。后面栈顶元素碰到null,说明null的下一个栈顶元素就是访问过但还没处理的元素,后面就可以对null进行出栈,保存下一个栈顶元素,然后栈顶元素出栈,将保存的元素值加入res

代码如下:

//迭代统一版
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res=new ArrayList<>();
        if(root==null)return res;
        Deque<TreeNode> stack=new LinkedList<>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur=stack.pop();//栈顶出栈,并让cur存储出栈元素
            if(cur!=null){//出栈栈顶元素的非
                // 判断出栈元素左右子树是否为空,不为空就入栈
                if(cur.right!=null)stack.push(cur.right);
                if(cur.left!=null)stack.push(cur.left);
                //因为是前序遍历(中左右),入栈要按右左中的顺序
                stack.push(cur);
                stack.push(null);//在中结点入栈后,null入栈作为标记;
            }else{//若栈顶元素为空,说明下一个栈顶元素是“中”结点
                cur=stack.pop();;//null的下一个栈顶元素就是“中结点”,用cur暂存
                res.add(cur.val);
            }
        }
        return res;
    }
}

LeetCode 145. 二叉树的后序遍历

原题链接

2023.06.11 二刷

递归版本代码如下:

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res=new LinkedList<>();
        postOrder(res,root);
        return res;
    }
    public void postOrder(List<Integer> res,TreeNode root){
        if(root==null)return;
        postOrder(res,root.left);
        postOrder(res,root.right);
        res.add(root.val);
    }
}

迭代版本,修改自前序遍历
前序遍历:中左右,调整代码入栈顺序->中右左,反转res->左右中(后续遍历)

代码如下:

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res =new Arraylist<>();
        if(root==null)return res;
        Deque<TreeNode> stack=new LinkedList<>();
        stack.push(root);
        while(!stack.isEmpty()){
            root=stack.pop();
            res.add(root.val);
            if(root.left!=null)stack.push(root.left);
            if(root.right!=null)stack.push(root.right);
        }
        Collections.reverse(res);
        return res;
    }
}

迭代法,统一模板,代码如下:

//统一版本
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res=new ArrayList<>();
        if(root==null)return res;
        Deque<TreeNode> stack= new LinkedList<>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur=stack.pop();
            if(cur!=null){
                stack.push(cur);
                stack.push(null);
                if(cur.right!=null)stack.push(cur.right);
                if(cur.left!=null)stack.push(cur.left);
            }else{
                cur=stack.pop();
                res.add(cur.val);
            }
        }
        return res;

    }
}

LeetCode 94. 二叉树的中序遍历

原题链接

2023.06.11 二刷

递归版本,代码如下:

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new LinkedList<>();
        inOrder(res,root);
        return res;
    }
    public void inOrder(List<Integer> res,TreeNode root){
        if(root==null)return;
        inOrder(res,root.left);
        res.add(root.val);
        inOrder(res,root.right);
        
    }
}

迭代法,非统一模板

中序遍历与前序和后序遍历不太一样
前序遍历的时候,遍历结点和处理结点是一起前后进行的,而中序遍历无法这样

代码如下:


class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res =new LinkedList<>();
        if(root==null)return res;
        Stack<TreeNode> stack =new Stack<>();
        TreeNode cur=root;//用cur来遍历树
        //当栈或当前遍历结点非空的时候
        while(!stack.isEmpty()||cur!=null){
            if(cur!=null){//当前遍历结点非空,就要入栈,然后往左子树遍历
                stack.push(cur);
                cur=cur.left;//左
            }else{//当前遍历结点为空
                cur=stack.peek();//栈顶元素就是要处理的数据,用cur保存
                stack.pop();
                res.add(cur.val);//中
                cur=cur.right;//再往右子树遍历
            }
        }
        return res;
    }
}

迭代法,统一模板,代码如下:

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res= new ArrayList<>();
        if(root==null)return res;
        Deque<TreeNode> stack=new LinkedList<>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur=stack.pop();
            if(cur!=null){ 
                if(cur.right!=null)stack.push(cur.right);
                stack.push(cur);
                stack.push(null);
                if(cur.left!=null)stack.push(cur.left);
            }else{
                cur=stack.pop();
                res.add(cur.val);
            }
        }
        return res;
    }
}

n叉树的遍历

LeetCode 589. N 叉树的前序遍历

原题链接

//递归法
class Solution {
    List<Integer> res=new ArrayList<>();
    public List<Integer> preorder(Node root) {
        if(root==null)return res;
        res.add(root.val);
        for(Node child:root.children)preorder(child);
        return res;
    }
}

/*
前序遍历顺序是根-左-右,每层循环里面都是根结点先出栈,加入res,可以保证结果集中根会在孩子结点前;
还需要解决左右的顺序,栈先进后出的特性,要先让右孩子入栈,这样后面出栈可让左孩子先出栈加入res,保证结果集中的左-右顺序;结合起来就是根-左-右的顺序;
*/
//借助栈实现的迭代法
class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> res=new ArrayList<>();
        if(root==null)return res;
        Stack<Node>stack=new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            root=stack.pop();
            res.add(root.val);
            //因为栈先进后出,所以需要先让右边的孩子结点入栈
            for(int i=root.children.size()-1;i>=0;--i)stack.push(root.children.get(i));
        }
        return res;
    }
}


LeetCode 590. N 叉树的后序遍历

原题链接

//递归法
class Solution {
    List<Integer> res=new ArrayList<>();
    public List<Integer> postorder(Node root) {
        if(root==null)return res;
        for(Node child:root.children)postorder(child);
        res.add(root.val);
        return res;
    }
}

/*
与前序遍历迭代改后序遍历迭代类似,前序是根-左-右,后序是左-右-根;
只要在入栈时,让左孩子先于右孩子入栈,这样出栈就是右孩子先出,保证右-左顺序加入res;
而根总是先于孩子结点入栈,在孩子结点入栈的那层循环里,根结点值已经加入res;
这样可以保证结果集里是根-右-左,只要最后翻转res就可以变成左-右-根;
*/
//后序迭代法
class Solution {
    public List<Integer> postorder(Node root) {
        List<Integer> res=new ArrayList<>();
        if(root==null)return res;
        Stack<Node>stack=new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            root=stack.pop();
      			 res.add(root.val);
            for(int i=0;i<root.children.size();++i)
                stack.push(root.children.get(i));
        }
        Collections.reverse(res);
        return res;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值