二叉树算法专栏一《理论基础》

news2024/11/30 6:47:55

下面我会介绍一些我在刷题过程中经常用到的二叉树的一些基础知识,所以我不会教科书式地将二叉树的基础内容通通讲一遍。

二叉树的种类

在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。

满二叉树

满二叉树是一种特殊的二叉树,具有以下特点:

  • 在满二叉树中,每个节点要么没有子节点(度为0),要么恰好有两个子节点(度为二)。
  • 对于深度为 k 的满二叉树,其节点数目为 2^k - 1,其中 k ≥ 1。也就是说,深度为 k 的满二叉树总共有 2^k - 1 个节点。
  • 满二叉树的结构非常规整,每一层的节点数都是满的,且节点的分布非常均匀。

完全二叉树

完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点

下面我来举一个例子,看大家是否判断正确了呢

图3不是完全二叉树,因为叶子节点的那一层的所有叶子节点不是全集中在最左侧的位置。

几种特殊的二叉树

二叉搜索树

前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

如下图就是两个二叉搜索树

平衡二叉搜索树

平衡二叉搜索树(Balanced Binary Search Tree,BBST),也称为自平衡二叉搜索树,是一种特殊的二叉搜索树,它能够在插入或删除节点时自动保持平衡。

二叉搜索树是一种有序的二叉树,其任意节点的值都大于其左子树中任意节点的值,小于其右子树中任意节点的值。但是,当我们在普通的二叉搜索树上插入或删除节点时,可能会出现树的不平衡,导致搜索树的时间复杂度退化为 O(n)。而平衡二叉搜索树通过旋转、重新分配节点等方式自动调整树的结构,使得树保持平衡,从而能够更快地进行查找、插入、删除等操作,时间复杂度能够保持在 O(log n)。

常见的平衡二叉搜索树包括AVL树、红黑树、Splay树、Treap等。

二叉平衡树具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

如下图

最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。

java中的TreeMap、HashMap等容器底层都是平衡二叉搜索树实现的。

二叉树的存储方式

二叉树可以链式存储,也可以顺序存储。

链式存储

二叉树的链式存储是指使用节点对象和引用来表示二叉树的存储方式。每个节点对象包含一个数据域和两个指针域,分别指向左子节点和右子节点。因此链式存储在内存上是不连续的

在链式存储中,通过创建节点对象,并通过引用将节点对象连接起来,形成二叉树的结构。根节点作为入口点,通过左右子节点的引用,逐层连接形成完整的二叉树。

下面是链式存储的示例代码:

package dataStructure.binaryTree;


/**
 * @author CSDN编程小猹
 * @data 2023/12/05
 * @description
 */
//树节点类
public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

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

    @Override
    public String toString() {
        return String.valueOf(this.val);
    }
}

链式存储如图:

顺序存储

顺序存储就是将二叉树的节点按照某种顺序存储在数组中,因此顺序存储的元素在内存上是连续分布的。顺序存储方式通过数组的索引关系来表示节点之间的父子关系。

对于一棵完全二叉树,可以使用数组进行顺序存储。假设根节点在数组索引0的位置,对于任意节点 i,它的左子节点索引为 2i+1右子节点索引为 2i+2。这样,我们可以利用数组的连续内存空间来存储二叉树的节点。

下面是顺序存储的示例代码:

package dataStructure.binaryTree;


/**
 * @author CSDN编程小猹
 * @data 2023/12/05
 * @description
 */
public class BinaryTree {
    int[] array;
    int size;

    public BinaryTree(int capacity) {
        this.array = new int[capacity];
        this.size = 0;
    }

    public void insert(int data) {
        if (size >= array.length) {
            throw new ArrayIndexOutOfBoundsException("Binary tree is full");
        }

        array[size++] = data;
    }

    public int getRoot() {
        if (size == 0) {
            throw new IllegalStateException("Binary tree is empty");
        }

        return array[0];
    }

    public int getLeftChild(int index) {
        int leftChildIndex = 2 * index + 1;
        if (leftChildIndex >= size) {
            throw new IllegalArgumentException("Invalid index: " + index);
        }

        return array[leftChildIndex];
    }

    public int getRightChild(int index) {
        int rightChildIndex = 2 * index + 2;
        if (rightChildIndex >= size) {
            throw new IllegalArgumentException("Invalid index: " + index);
        }

        return array[rightChildIndex];
    }

}

顺序存储如图:

二叉树的遍历方式

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

  • 深度优先遍历:先往深走,遇到叶子节点再往回走。
  • 广度优先遍历:一层一层的去遍历。

深度优先遍历

前序遍历

口诀:根左右

具体的前序遍历过程如下:

  • 访问当前节点(根节点)。
  • 递归地前序遍历左子树。
  • 递归地前序遍历右子树。

前序遍历有两种方法,分别是递归法和迭代法。

递归法
  /**
     * <h3>前序遍历</h3>
     * @param node 节点
     */
    static void preOrder(
            //确定入参和返回值
            TreeNode node) {
        //终止条件
        if (node == null) {
            return;
        }
        //单层递归的逻辑
        System.out.print(node.val + "\t"); // 值
        preOrder(node.left); // 左
        preOrder(node.right); // 右
    }
迭代法
//迭代遍历二叉树
     前序遍历顺序:中-左-右,入栈顺序:中-右-左
    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 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;
    }

中序遍历

口诀:左根右

具体的前序遍历过程如下:

  • 递归地中序遍历左子树
  • 访问当前节点(根节点)
  • 递归地中序遍历右子树

前序遍历有两种方法,分别是递归法和迭代法。

递归法
 /**
     * <h3>中序遍历</h3>
     * @param node 节点
     */
    static void inOrder(TreeNode node) {
        if (node == null) {
            return;
        }
        inOrder(node.left); // 左
        System.out.print(node.val + "\t"); // 值
        inOrder(node.right); // 右
    }
迭代法
 // 中序遍历顺序: 左-中-右 入栈顺序: 左-右
        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 (cur != null || !stack.isEmpty()){
                if (cur != null){
                    stack.push(cur);
                    cur = cur.left;
                }else{
                    cur = stack.pop();
                    result.add(cur.val);
                    cur = cur.right;
                }
            }
            return result;
        }

后序遍历

口诀:左右根

具体的前序遍历过程如下:

  • 递归地后序遍历左子树
  • 递归地后序遍历右子树
  • 访问当前节点(根节点)

前序遍历有两种方法,分别是递归法和迭代法。

递归法
   /**
     * <h3>后序遍历</h3>
     * @param node 节点
     */
    static void postOrder(TreeNode node) {
        if (node == null) {
            return;
        }
        postOrder(node.left); // 左
        postOrder(node.right); // 右
        System.out.print(node.val + "\t"); // 值
    }
迭代法
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
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 node = stack.pop();
        result.add(node.val);
        if (node.left != null){
            stack.push(node.left);
        }
        if (node.right != null){
            stack.push(node.right);
        }
    }
    Collections.reverse(result);
    return result;
}

看了上面的迭代法遍历二叉树,读者是不是发现每一种不同顺序的遍历代码都有较大的改动。下面介绍一下二叉树的统一迭代法

//二叉树的统一迭代法
    //前序遍历
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;
}
//中序遍历
    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;
    }
    //后序遍历
    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;
    }

读者可以自己遍历一下下图这个二叉树,看你是否掌握了呢?

广度优先遍历

层次遍历

二叉树的层序遍历是一种广度优先搜索(BFS)的遍历方式,按照树的层级依次访问节点。从根节点开始,逐层遍历二叉树的节点。层序遍历可以保证按照从上到下、从左到右的顺序访问二叉树的节点,输出的结果就是二叉树节点的层级顺序。

例如,对于下面的二叉树:

层序遍历的结果为:1, 2, 3, 4, 5, 6。首先访问根节点1,然后是第二层的节点2和3,接着是第三层的节点4、5和6。

与上面的深度优先遍历一样,层次遍历也有递归法和迭代法两者遍历方式。

递归法
public List<List<Integer>> resList=new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root){
        checkFun02(root,0);
            return resList;

    }
//递归方式进行层序遍历
    private void checkFun02(
            //递归要传的参数
            TreeNode root, int deep) {
        //终止条件
        if (root==null) return;
        //递归单层逻辑
        deep++;
        if (resList.size()<deep){
            //当层级增加,resList的Item也增加,利用resList的索引值进行层级界定
            List<Integer> item=new ArrayList<>();
            resList.add(item);
        }
        //往所在层的集合添加该节点元素
        resList.get(deep-1).add(root.val);
        //递归调用
        checkFun02(root.left,deep);
        checkFun02(root.right,deep);
    }
迭代法
    public List<List<Integer>> resList=new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root){
        checkFun01(root);
            return resList;

    }
//迭代方式进行层序遍历--借助队列
    private void checkFun01(TreeNode root) {
       if (root==null) return;
       Queue<TreeNode> queue=new LinkedList<TreeNode>();
       queue.offer(root);
       while (!queue.isEmpty()) {
           List<Integer> itemList=new ArrayList<>();
           int len=queue.size();
           while (len>0){
               TreeNode tempNode=queue.poll();
               itemList.add(tempNode.val);
               if (tempNode.left!=null) queue.offer(tempNode.left);
               if (tempNode.right!=null) queue.offer(tempNode.right);
               len--;
           }
           resList.add(itemList);
       }
    }

二叉树是一种基础数据结构,在算法面试中都是常客,也是众多数据结构的基石。

本篇我介绍了二叉树的种类、存储方式、遍历方式,比较全面的介绍了二叉树各个方面的重点,帮助大家扫一遍基础。后续我会再写一些关于二叉树刷题的博文。都看到这里了,读者大大就点个关注吧,你们的支持是我持续更新的最大动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1299696.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

优麒麟ubuntukylin安装UE4.27.2

优麒麟ubuntukylin安装UE4.27.2 在&#xff08;国产&#xff09;优麒麟 ubuntukylin Linux平台上编译测试安装虚幻引擎。 优麒麟系统 这里选择的是官方增强版 https://www.ubuntukylin.com/downloads/ 同样的可以选择对应的Ubuntu22.04 LTS&#xff0c;唯一的区别就是优麒麟…

java--Math、System、Runtime

1.Math 代表数字&#xff0c;是一个工具类&#xff0c;里面提供的都是对数据进行操作的一些静态方法。 2.Math类提供的常见方法 3.System System代表程序所在的系统&#xff0c;也是一个工具类。 4.System类提供的常见方法 5.时间毫秒值 值的是从1970年1月1日 00:00:00走到…

Realme X7 Pro Root 刷机教程

Realme X7 Pro 刷机教程 Just For Fun&#xff0c;最近倒腾了下Realme X7 Pro 刷root。此博客为个人记录刷机过程&#xff0c;如有机友跟随本教程操作&#xff0c;请谨慎操作&#xff01;&#xff01;&#xff01; 以下教程真针对Realme X7 Pro&#xff0c;其他版本方法未知&…

智能优化算法应用:基于斑马算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于斑马算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于斑马算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.斑马算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

多维时序 | MATLAB实现RIME-CNN-LSTM-Multihead-Attention多头注意力机制多变量时间序列预测

多维时序 | MATLAB实现RIME-CNN-LSTM-Multihead-Attention多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实现RIME-CNN-LSTM-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现RIME-CNN-…

【rabbitMQ】rabbitMQ用户,虚拟机地址(添加,修改,删除操作)

rabbitMQ的下载&#xff0c;安装和配置 https://blog.csdn.net/m0_67930426/article/details/134892759?spm1001.2014.3001.5502 rabbitMQ控制台模拟收发消息 https://blog.csdn.net/m0_67930426/article/details/134904365?spm1001.2014.3001.5502 目录 用户 添加用户…

node.js安装和配置

软件介绍 Node.js是一个免费的、开源的、跨平台的JavaScript运行时环境&#xff0c;允许开发人员在浏览器之外编写命令行工具和服务器端脚本。 Node.js是一个基于Chrome JavaScript运行时建立的一个平台。 Node.js是一个事件驱动I/O服务端JavaScript环境&#xff0c;基于Googl…

在线网页生成工具GrapesJS

项目地址 https://github.com/GrapesJS/grapesjshttps://github.com/GrapesJS/grapesjs 项目简述 这是一个基于node.js的在线网页生成项目&#xff0c;对简化开发有很大的帮助。 主要使用的语言如下&#xff1a; 编辑页面如下&#xff1a; 使用也很简洁 具体可以看下项目。…

NYX靶场

信息收集 # Nmap 7.94 scan initiated Fri Nov 24 21:59:30 2023 as: nmap -sn -oN live.nmap 192.168.182.0/24 Nmap scan report for 192.168.182.1 (192.168.182.1) Host is up (0.00044s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap scan report for 192.168…

Java - JVM内存区域的划分

Java 程序运行时&#xff0c;需要在内存中分配空间。为了提高运算效率&#xff0c;就对空间进行了不同区域的划分&#xff0c;因为每一片区域都有特定的处理数据方式和内存管理方式。 分配&#xff1a;通过关键字new创建对象分配内存空间&#xff0c;对象存在堆中。 释放 &…

springboot095学生宿舍信息的系统

springboot095学生宿舍信息的系统 源码获取&#xff1a; https://docs.qq.com/doc/DUXdsVlhIdVlsemdX

Python轴承故障诊断 (四)基于EMD-CNN的故障分类

目录 前言 1 经验模态分解EMD的Python示例 2 轴承故障数据的预处理 2.1 导入数据 2.2 制作数据集和对应标签 2.3 故障数据的EMD分解可视化 2.4 故障数据的EMD分解预处理 3 基于EMD-CNN的轴承故障诊断分类 3.1 训练数据、测试数据分组&#xff0c;数据分batch 3.2 定义…

SSL证书更新

首先&#xff0c;我们需要理解为什么需要更新SSL证书。SSL证书的有效期通常为一年。一旦证书过期&#xff0c;浏览器会显示警告&#xff0c;提示用户该网站的SSL证书已经过期&#xff0c;这可能会导致用户对网站的信任度下降&#xff0c;甚至直接离开网站。此外&#xff0c;一些…

推荐一个界面设计软件aardio,配合python三分钟制作一个小软件。【批量doc文件转docx文件】

文章目录 前言一、aardio软件代码二、python代码总结 前言 aardio这个软件不多说&#xff0c;好用方便。 一、aardio软件代码 import win.ui; /*DSG{{*/ mainForm win.form(text"批量doc文件转docx文件";right623;bottom171) mainForm.add( button{cls"butto…

案例015:基于微信小程序的校园防疫系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

[论文浅谈] Can ChatGPT Replace Traditional KBQA Models?

一 背景意义 ChatGPT是一款强大的大型语言模型&#xff08;LLM&#xff09;&#xff0c;它通过学习大量文本数据&#xff0c;包括维基百科等知识资源&#xff0c;使其具备广泛的自然语言理解能力。ChatGPT通过利用自己的知识展示的强大的问答能力使人们对于探索ChatGPT是否能够…

SpringMVC修炼之旅(3)REST风格与拦截器

一、概述 1.1简介 Restful就是一个资源定位及资源操作的风格。不是标准也不是协议&#xff0c;只是一种风格。基于这个风格设计的软件可以更简洁&#xff0c;更有层次&#xff0c;更易于实现缓存等机制。 1.2功能 资源&#xff1a;互联网所有的事物都可以被抽象为资源 资源操作…

CleanMyMac X2024最新专业苹果电脑mac系统垃圾清理软件

CleanMyMac X是一款颇受欢迎的专业清理软件&#xff0c;拥有十多项强大的功能&#xff0c;可以进行系统清理、清空废纸篓、清除大旧型文件、程序卸载、除恶意软件、系统维护等等&#xff0c;并且这款清理软件操作简易&#xff0c;非常好上手&#xff0c;特别适用于那些刚入手苹…

VSCode Keil Assintant 联合开发STM32

文章目录 VSCodeKeil AssistantUV5&#x1f947;软件下载&#x1f947;配置环境&#x1f947;插件安装&#x1f948;C/C Extension Pack&#x1f949;C/C Extension Pack介绍&#x1f949;插件安装 &#x1f948;Keil Assistant&#x1f949;Keil Assistant介绍&#x1f949;插…

onnxruntime和tensorrt多batch推理

以lenet网络为例。 onnxruntime多batch推理 当batch size为2时&#xff0c;导出如下结构的onnx文件&#xff1a; python推理&#xff1a; import cv2 import numpy as np import onnxruntimeimg0 cv2.imread("2.png", 0) img1 cv2.imread("10.png", …