LeetCode刷题小记——二叉树的先序遍历

这篇博客详细介绍了如何解决LeetCode中的树的先序遍历问题,包括递归和非递归两种方法。递归方式简洁易懂,非递归则分为模拟系统栈和手工辅助栈两种实现。对于非递归,作者推荐了一种适用于中序与后序遍历的写法,通过栈的特点调整节点入栈顺序,实现了通用的遍历。博客还对比了不同方法的优缺点,帮助读者理解并选择适合自己的解题策略。
摘要由CSDN通过智能技术生成

LeetCode刷题小记——树的先序遍历

没事就来刷一刷

题目

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

输入:root = [1,null,2,3]
输出:[1,2,3]

总共分两大类:

  • 递归
  • 非递归(迭代)
    • 模拟系统栈
    • 手工辅助栈
      • 写法一(好理解,但不通用)
      • 写法二(较麻烦,但同时适用中序与后序,强烈推荐

递归

递归就不说了,太简单。

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        preorder(root, res);
        return res;
    }

    public void preorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        res.add(root.val);
        preorder(root.left, res);
        preorder(root.right, res);
    }
}

非递归

有两种解法:

  • 模仿系统栈(纯模仿)
  • 使用手工栈辅助(好写)

模仿系统栈

特点:入栈时访问

思想:

遍历到最左边的节点(同时入栈访问),直至为空,然后取出栈顶节点,把其右孩子压栈。

代码:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        //手动建立栈
        Deque<TreeNode> stack = new LinkedList<>();
        List<Integer> list = new ArrayList<>();
        TreeNode node;
        //先判空
        if(root == null) return list;
        node = root;
        while(!stack.isEmpty() || node!=null){ 
            while(node!=null){
                list.add(node.val);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;//右孩子为根
        }
        return list;
    }
}

手工栈辅助

写法一

特点:出栈时访问

思想:出栈时访问,由于访问的顺序是中,左,右。而栈的后进先出的,所以先让右孩子入栈(后访问),再让左孩子入栈。

注意:这种方式好理解,也好写,但是并不是模拟系统栈的迭代写法。

步骤:

  1. 数据初始化,先把根节点压栈。
  2. 栈顶出栈,访问。
  3. 如果右孩子不为空,入栈。
  4. 如果左孩子不为空,入栈。
  5. 重复2-4。

代码:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        //手动建立栈
        Deque<TreeNode> stack = new LinkedList<>();
        List<Integer> list = new ArrayList<>();
        TreeNode node;
        //先判空
        if(root == null) return list;
        stack.push(root);//先把根节点入栈
        while(!stack.isEmpty()){ 
            //出栈访问
            node = stack.pop();
            list.add(node.val);
            //先加入右孩子
            if(node.right!=null)
                stack.push(node.right);
            //再加入左孩子
            if(node.left!=null)
                stack.push(node.left);
        }
        return list;
    }
}

写法二

思想:利用栈的特点,把节点按照逆序放进去,如先序遍历,放入节点顺序为(右,左,中)。

以当前子树为单位,放入栈。

这里借鉴了大佬的颜色标记法。本文用节点类型代替颜色标识。

在放入当前子树根节点的时候,放的是值,不是节点。以此来代替颜色标记。

此代码思想适用于先序遍历与后序遍历,强烈推荐!!!

代码:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        //初始化
        List<Integer> res = new ArrayList<>();//存放结果
        Stack stack = new Stack<>();//手工栈
        TreeNode node;//工具人
        //先判空
        if(root == null) return res;
        node = root;
        stack.push(node);
        while(!stack.isEmpty()){
            Object o = stack.pop();
            if(o instanceof TreeNode){//如果是个未访问子树,压栈
                node = (TreeNode)o;
                //三个节点压栈,按照后进先出的原则,注意根节点放的是值,不是节点。
                if(node.right != null)
                    stack.push(node.right);
                if(node.left != null)
                    stack.push(node.left);
                stack.push(node.val);	//放的是值,以此来判断当前子树是否已被压栈。
            }else{					//如果是个已访问子树,直接放结果
                res.add((Integer)o);
            }
        }
        return res;
    }
}

结语:

个人感觉,非递归写法中,手工辅助栈的写法一较为容易理解,也好写。

但是写法二适用于中序与后序遍历,强烈推荐!!!

看个人习惯,喜欢哪个用那个。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值