算法刷题 8 二叉树【基础知识(深度广度)+多种遍历方法(递归+迭代)和相关简单应用(层序遍历拓展)】

目录

基础概念

遍历的形式

二叉树的定义

递归逻辑

二叉树的递归遍历

前序遍历--中左右

二叉树的迭代遍历

前序遍历

后序遍历

中序遍历

层序遍历 

层序遍历拓展题目

1、如果要倒序输出

199 二叉树右视图

429 n叉树的层序遍历

116 填充每个节点的下一个右侧节点指针

104 二叉树的最大深度​编辑

111 二叉树的最小深度


基础概念

  • 二叉搜索树----左小右大
  • 平衡二叉搜索树 ----左右子树差不能超过1

    遍历的形式

  1. 深度优先遍历
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  2. 广度优先遍历
    • 层次遍历(迭代法)
  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左右中

二叉树的定义

val + 左右指针

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

递归逻辑

  1. 终止条件
  2. 如何变小-确定递归函数的参数和返回值
  3. 单层逻辑操作

二叉树的递归遍历

记住递归三要素--开始遍历

前序遍历--中左右

思路:当前层把节点add到list里,递归到左右子树,停止则root为null则return退出当前递归

// 前序遍历·递归·LC144_二叉树的前序遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        preorder(root, result);
        return result;
    }

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

中序和后序就是把preorder和add值的顺序变了下即可


二叉树的迭代遍历

前序遍历

前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。

动画如下:

二叉树前序遍历(迭代法)

List<Integer> result = new ArrayList<>();
        if (root == null) return result;//不能return null,空和null不一样
        Stack<TreeNode> stack = new Stack<>();//迭代法二叉树用栈存
        stack.push(root);//首先放根节点
        while (!stack.isEmpty()) {//条件是栈不空
        //大体逻辑是出来一个判断左右子节点,有的话就放进去,没有就继续pop
        //不过要注意先放右节点,才能左先出
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left != null) {
                stack.push(node.left);
            }
        }
        return result;
    }

创一个栈+放入根节点+(在栈不空的条件下弹出,加入右左节点)

后序遍历

再来看后序遍历,先序遍历是中左右,后序遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图:

前序到后序

List<Integer> result = new ArrayList<>();
        if (root == null) return result;
        Stack<TreeNode> stack = new Stack<>();
        Stack<Integer> output = new Stack<>();//毕竟是反过来存着之后还要返回来,那可以用个栈1正好
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            output.push(node.val);
            if (node.left != null) {
                stack.push(node.left);
            }
            if (node.right != null) {
                stack.push(node.right);
            }
        }
        while (!output.isEmpty()) {
            result.add(output.pop());
        }
        return result;

先序便利的左右子树存入顺序交换,然后翻转list,中左右-〉中右左-〉左右中

这里用的2个stack,一个遍历,一个存值

也可以直接反转result,注意list的操作:取get,更新值set(i, value),大小size()

for (int i = 0, j = result.size() - 1; i < j; i++, j--) {
     //直接反转list,注意list的操作
      int temp = result.get(i);
      result.set(i, result.get(j));
      result.set(j, temp);
 }

 中序遍历

. - 力扣(LeetCode)

为了解释清楚,我说明一下刚刚在迭代的过程中,其实我们有两个操作:

  1. 处理:将元素放进result数组中
  2. 访问:遍历节点

分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。

那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

动画如下:

二叉树中序遍历(迭代法)

public List<Integer> inorderTraversal(TreeNode root) {
        //中序 左中右 要一个指针
        List<Integer> result = new ArrayList<>();
        TreeNode cur = root;
        Stack<TreeNode> stack = new Stack<>();
        if (root == null) return result;
        while (cur != null || !stack.isEmpty()) {
            if (cur != null) { //找到最左节点,这里不能.left,就是用自身判断
                stack.push(cur);
                cur = cur.left;
            } else {//找到之后就弹出记录,再看看右节点有没有
                cur = stack.pop();//这里要更新cur了,因为上一步为了返回已经把cur变为空了
                result.add(cur.val);
                cur = cur.right;
            }
        }
        return result;
    }

中序 左中右 要一个指针,找到最左下方不断push,然后进行弹出记录,再指向右边继续


层序遍历 

需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。

public List<List<Integer>> levelOrder(TreeNode root) {
        //队列 
        List<List<Integer>> resList = new ArrayList<List<Integer>>();
        if (root == null) return resList;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);
        while (!que.isEmpty()) {
            //得一层一层存
            List<Integer> itemList = new ArrayList<Integer>();
            int len = que.size();
            while (len > 0) {
                TreeNode node = que.poll();
                itemList.add(node.val);
                if (node.left != null) que.offer(node.left);
                if (node.right != null) que.offer(node.right);
                len--;
            }
            resList.add(itemList);
        }
        return resList;
    }

不能依赖二叉树的自己结构---用队列

要用一个size快照每一层的数量,才知道要弹出多少

进一层,记size,然后出size个,找有没有子节点,有的话就把子节点放进去 


层序遍历拓展题目

1、如果要倒序输出

        List<List<Integer>> result = new ArrayList<>();
        for (int i = list.size() - 1; i >= 0; i-- ) {
            result.add(list.get(i));
        }

199 二叉树右视图

力扣题目链接

其实就是层次遍历小变,取每层最后一个数就行了,不是要记录size吗,每层size=1的时候记下来即可

 

429 n叉树的层序遍历

变一下放入子节点

for (int i = 0; i < node.children.size(); i++) {
      if (node.children.get(i) != null) {
            que.offer(node.children.get(i));
            }
        }

116 填充每个节点的下一个右侧节点指针

关键核心在于:每一层取出一个节点后,指向队列内下一个节点,除了最后一个指向空即可

que.peek(): 不改变元素的情况下得到最顶上的值

 if(len != 1) 
     node.next = que.peek();//peek是不改变元素的情况下得到最顶上的值
 else 
     node.next = null;

 104 二叉树的最大深度

就是层序遍历记录一下层数 

111 二叉树的最小深度

思路: 如果一个节点没有左右孩子,那这个就是最小深度了,所以在层次遍历的时候进行一下判断即可

if (node.right == null && node.left == null) 
    return depth + 1;

所以,二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时又发现队列的一个应用了)。 

那么,以上,便是二叉树的基础知识遍历和相关简单应用啦~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值