二叉树part01

1.二叉树的遍历递归

二叉树的前序遍历
二叉树的中序遍历
二叉树的后序遍历
递归的一般步骤
1.确定递归函数的参数和返回值
返回值一般都是void,因为都把结果都放在参数里了。可以在写递归逻辑的过程中逐步确认需要用到的参数。
2.确定终止条件
报栈溢出:通常是终止条件没有确认好
3.确定单层递归的逻辑
在这里也就会重复调用自己来实现递归的过程。
✅“序”指的是根结点的顺序
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
✅Java函数传参,当参数为引用类型时,传递的是引用(地址),所以在方法中对形参所进行的修改,将影响到实参。
✅以前序遍历为例,其他类似
(1)终止条件是root==null,而非判断root.left或root.right,因为即使当root没有子节点,仍然需要被访问并添加;

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

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

2.二叉树的迭代遍历

参考视频:清华大学邓俊辉数据结构与算法
✅前序遍历
最左侧通路(leftmost path):在二叉树T中,从根节点出发沿着左分支一直下行的那条通路(以粗线示意)。
在这里插入图片描述前序遍历序列可分解为两段:
(1)沿最左侧通路自顶而下访问的各节点。
(2)以及自底而上遍历的对应右子树。
在写迭代算法时,结合直接后继的想法会更加清楚。在前序遍历中,节点的直接后继为左孩子,若不存在则为右孩子,若仍不存在则为父节点的右孩子(右兄弟节点)。----->所以才会将右孩子入栈

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list=new LinkedList<>();
        LinkedList<TreeNode> stack=new LinkedList<>();
        visitLefBranch(root,list,stack);
        while(!stack.isEmpty()){
            TreeNode node=stack.pop();
            visitLefBranch(node,list,stack);
        }
        return list;
    }

    //从node节点出发,沿左分支不断深入,直至没有左分支的节点;沿途节点遇到后立即访问
    public void visitLefBranch(TreeNode node, List<Integer> list, LinkedList<TreeNode> stack){
        while(node!=null){
            list.add(node.val);
            //存储右孩子
            if(node.right!=null){
                stack.push(node.right);
            }
            //沿左侧分支向下
            node=node.left;
        }
    }
}

✅中序遍历
沿用最左侧通路,只是略有不同。
中序遍历序列可分解为两段:
(1)沿最左侧通路自顶而下,找到最深的节点,并将沿途节点存入栈中。
(2)访问当前节点。
(3)遍历以其右孩子(若存在)为根的子树。
在这里插入图片描述中序遍历的实质功能也可理解为,为所有节点赋予一个次序,从而将半线性的二叉树转化为线性结构。于是,用直接后继的方法更容易理解。在中序遍历(左根右)中,节点的直接后继为右孩子,若右孩子存在再沿该子树的最左侧通路朝左下方深入,直到抵达子树中最靠左(最小) 的节点即可。若不存在,则直接后继为节点的父节点。----将所有节点存入栈中(需要用到父节点)。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list=new LinkedList<>();
        LinkedList<TreeNode> stack=new LinkedList<>();
        visitLeftBranch(root,stack);
        while(!stack.isEmpty()){
            TreeNode node=stack.pop();
            //访问当前节点
            list.add(node.val);
            //遍历右子树
            if(node.right!=null){
                visitLeftBranch(node.right,stack);
            }
        }
        return list; 
    }

    //深入最左侧通路
    public void visitLeftBranch(TreeNode node,LinkedList<TreeNode> stack){
        while(node!=null){
            stack.push(node);
            node=node.left;
        }
    }
}

✅后序遍历
最左侧可见通路(LR Branch) : 从左侧水平向右看去,未被遮挡的节点,即为后序遍历首先访问的节点。请注意,这些节点既 可能是左孩子,也可能是右孩子
后序遍历序列可分解为三段:
(2)访问当前节点。
(3)遍历以其右孩子(若存在)为根的子树,沿最左侧可见通路自顶而下,找到最深的节点,并将沿途节点即其右孩子都存入栈中。
(4)回溯至父节点。
在后续遍历中,直接后继为右兄弟节点,若右兄弟节点存在则沿其最左侧可见通路遍历,若不存在则回溯至当前节点的父亲节点。
在这里插入图片描述

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list=new LinkedList<>();
        LinkedList<TreeNode> stack=new LinkedList<>();
        visitLRBranch(root,stack);
        while(!stack.isEmpty()){
            TreeNode node=stack.pop();
             //访问当前节点
            list.add(node.val);
            //栈顶不是当前元素的父节点而是右兄弟
            if(!stack.isEmpty() && stack.peek().right!=node && stack.peek().left!=node){ 
                //遍历右兄弟子树
                visitLRBranch(stack.pop(),stack);
            }
        }
        return list; 
    }
    public void visitLRBranch(TreeNode node,LinkedList<TreeNode> stack){
        while(node!=null){
            stack.push(node);
            //优先左孩子
            if(node.left!=null){
                //先push右子树(到时候访问顺序为左子树——》右子树——〉父亲节点
                if(node.right!=null){
                    stack.push(node.right);
                }
                node=node.left;
            }
            else{
                node=node.right;
            }
        }
    }
}

在后序遍历的实现中,必须将右孩子入栈而不是依赖于父节点的右子树判断。是因为后序遍历的顺序是先访问左子树,然后是右子树,最后是根节点。如果仅依赖于父节点的右子树判断,那么在遍历过程中,我们无法准确地知道右子树是否已经被访问过,因为右子树可能已经被弹出栈外。
在我们的实现中,当栈顶元素不是当前节点的父节点时,说明当前节点已经完成了对其左子树和右子树的访问,可以访问当前节点本身了。

3.二叉树的层序递归

原题链接
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑。
巧妙利用一个整数len,来进行层级判定。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        int len;
        List<List<Integer>> result=new LinkedList<>();
        List<Integer> level=new LinkedList<>();
        LinkedList<TreeNode> queue=new LinkedList<>();
        if(root==null){
            return result;
        }
        len=1;
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode node=queue.poll();
            level.add(node.val);
            if(node.left!=null){
                queue.offer(node.left);
            }
            if(node.right!=null){
                queue.offer(node.right);
            }
            len--;
            if(len==0){
                result.add(level);
                len=queue.size();
                level=new LinkedList<>();
            }
        }
        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值