代码学习记录12

本文详细介绍了二叉树的理论概念,包括满二叉树、完全二叉树、二叉搜索树、平衡二叉搜索树以及不同类型的遍历(递归与迭代),并提供了Java代码示例,展示了如何用统一迭代法实现先序、中序和后序遍历。
摘要由CSDN通过智能技术生成

随想录日记part12

t i m e : time: time 2024.03.05



主要内容:今天的主要内容是了解二叉树的理论基础,并且熟练掌握如何递归和迭代遍历二叉树。



Topic1二叉树理论基础

1.二叉树的题目分类:请添加图片描述
2.二叉树的分类:
2.1 满二叉树:

如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。请添加图片描述

2.2 完全二叉树:

在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h h h 层(从1开始),则该层包含 2 h − 1 2^{h-1} 2h1个节点。请添加图片描述

2.3 二叉搜索树:

叉搜索树是有数值的,二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 它的左、右子树也分别为二叉排序树请添加图片描述
2.4 平衡二叉搜索树(AVL树):

它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。如图:请添加图片描述
C + + C++ C++ m a p 、 s e t 、 m u l t i m a p , m u l t i s e t map、set、multimap,multiset mapsetmultimapmultiset 的底层实现都是平衡二叉搜索树, m a p 、 s e t map、set mapset 的增删操作时间复杂度是 l o g n logn logn,注意 u n o r d e r e d _ m a p unordered\_map unordered_map u n o r d e r e d _ s e t unordered\_set unordered_set 底层实现是哈希表。

2.5 二叉树存储方式:

二叉树可以链式存储,也可以顺序存储。
链式存储如图:
请添加图片描述
顺序存储的方式如图:
请添加图片描述

用数组来存储二叉树如何遍历的呢
如果父节点的数组下标是 i i i,那么它的左孩子就是 i ∗ 2 + 1 i * 2 + 1 i2+1,右孩子就是 i ∗ 2 + 2 i * 2 + 2 i2+2

2.6二叉树的遍历方式:

二叉树主要有两种遍历方式:

  • 深度优先遍历:先往深走,遇到叶子节点再往回走。
  • 广度优先遍历:一层一层的去遍历。
2.6.1 深度优先遍历:
  • 前序遍历(递归法,迭代法)
  • 中序遍历(递归法,迭代法)
  • 后序遍历(递归法,迭代法)
    请添加图片描述
2.6.2 广度优先遍历:
  • 层次遍历(迭代法)
2.7二叉树的节点定义:
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;
    }
}


Topic2二叉树的递归遍历

规则总结:
  • 1.确定递归函数的参数和返回值:
  • 2.确定终止条件
  • 3.确定单层递归的逻辑

java实现的代码如下:

class Solution {//递归前序遍历
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> tem=new ArrayList<Integer>();
        Traversal(root,tem);
        return tem;
    }
    void Traversal(TreeNode root,List<Integer> result){
        if(root==null)return;
        result.add(root.val);//中
        Traversal(root.left,result);//左
        Traversal(root.right,result);//右
    }
}
class Solution {//递归中序遍历
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> tem=new ArrayList<Integer>();
        Traversal(root,tem);
        return tem;
    }
    void Traversal(TreeNode root,List<Integer> result){
        if(root==null)return;
        Traversal(root.left,result);//左
        result.add(root.val);//中
        Traversal(root.right,result);//右
    }
}

class Solution {//递归后序遍历
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> tem=new ArrayList<Integer>();
        Traversal(root,tem);
        return tem;
    }
    void Traversal(TreeNode root,List<Integer> result){
        if(root==null)return;
        Traversal(root.left,result);//左
        Traversal(root.right,result);//右
        result.add(root.val);//中
    }
}


Topic3二叉树的迭代遍历

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。用栈也可以是实现二叉树的前后中序遍历。

  • 1.前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。动画如下:请添加图片描述
class Solution {// 递归前序遍历
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode tem = stack.pop();
            result.add(tem.val);//中
            if (tem.right != null)//右孩子入栈
                stack.push(tem.right);
            if (tem.left != null)//左孩子入栈
                stack.push(tem.left);
        }
        return result;
    }

  • 2.中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进 r e s u l t result result 数组中),这就造成了处理顺序和访问顺序是不一致的。那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。动画如下:请添加图片描述
class Solution {// 递归中序遍历
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (!stack.isEmpty() || cur != null) {
            if(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }else{
                cur=stack.pop();
                result.add(cur.val);
                cur=cur.right;
            }
        }
        return result;
    }

  • 3.后序遍历只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转 r e s u l t result result 数组,输出的结果顺序就是左右中了,如下图:请添加图片描述
class Solution {// 递归后序遍历
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode tem = stack.pop();
            result.add(tem.val);//中
            if (tem.left != null)//左孩子入栈
                stack.push(tem.left); 
            if (tem.right != null)//右孩子入栈
                stack.push(tem.right);
        }
        Collections.reverse(result);
        return result;
    }
}


Topic4二叉树的统一迭代法

三种遍历方式,使用迭代法是可以写出统一风格的代码,统一写法如下:

上面的方法无法解决同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记:就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。
详细看如下代码:

class Solution {//先序
   public List<Integer> preorderTraversal(TreeNode root) {
       List<Integer> result = new LinkedList<>();
       Stack<TreeNode> st = new Stack<>();
       if (root != null) st.push(root);
       while (!st.empty()) {
           TreeNode node = st.peek();
           if (node != null) {
               st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
               if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
               if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
               st.push(node);                          // 添加中节点
               st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
               
           } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
               st.pop();           // 将空节点弹出
               node = st.peek();    // 重新取出栈中元素
               st.pop();
               result.add(node.val); // 加入到结果集
           }
       }
       return result;
   }
}
class Solution {//中序
public List<Integer> inorderTraversal(TreeNode root) {
       List<Integer> result = new LinkedList<>();
   Stack<TreeNode> st = new Stack<>();
   if (root != null) st.push(root);
   while (!st.empty()) {
       TreeNode node = st.peek();
       if (node != null) {
           st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
           if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
           st.push(node);                          // 添加中节点
           st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。

           if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
       } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
           st.pop();           // 将空节点弹出
           node = st.peek();    // 重新取出栈中元素
           st.pop();
           result.add(node.val); // 加入到结果集
       }
   }
   return result;
}
}
class Solution {//后序
  public List<Integer> postorderTraversal(TreeNode root) {
       List<Integer> result = new LinkedList<>();
       Stack<TreeNode> st = new Stack<>();
       if (root != null) st.push(root);
       while (!st.empty()) {
           TreeNode node = st.peek();
           if (node != null) {
               st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
               st.push(node);                          // 添加中节点
               st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
               if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
               if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)         
                              
           } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
               st.pop();           // 将空节点弹出
               node = st.peek();    // 重新取出栈中元素
               st.pop();
               result.add(node.val); // 加入到结果集
           }
       }
       return result;
  }
}
  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值