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

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

二叉树的种类

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

满二叉树

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

  • 在满二叉树中,每个节点要么没有子节点(度为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);
       }
    }

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【头歌系统数据库实验】实验5 SQL的多表查询-1

目录 第1关&#xff1a;等值连接&#xff1a;求S表和J表城市相同的等值连接(列顺序还是按照S、J表) 第2关&#xff1a;查询供应情况&#xff0c;并显示供应商、零件和工程三者的名称 第3关&#xff1a;找出上海厂商供应的所有零件号码 第4关&#xff1a;找出使用上海产的零…

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;插…

CSS-自适应导航栏(flex | grid)

目标&#xff1a;实现左右各有按钮&#xff0c;中间是内容&#xff0c;自适应显示中间的内容导航栏&#xff0c;即 根据中间的宽度大小显示内容。 自适应导航栏 总结&#xff1a;推荐 flex布局 / grid布局 flex布局&#xff1a; 两侧 flex:1; ----->中间自适应 grid布局&…

Docker容器的可视化管理工具—DockerUI本地部署与远程访问

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

基于单片机智能病床呼叫系统设计

**单片机设计介绍&#xff0c;基于单片机智能病床呼叫系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能病床呼叫系统是一种利用单片机技术设计的医疗设备&#xff0c;它能够帮助病人在住院期间快速、方便…

flask之jinjia模板语法,拉取omdb api

模板主要的语法就是继承母版&#xff0c;集成模块。 继承母版的语法是&#xff1a; {% extends "common/home.html" %} 母版里集成模块的语法是&#xff1a; {% block head %}{% include ./common/header.html %}{% endblock %} 拉取电影资源&#xff0c;网址是&a…

T5论文个人记录

参考&转载自&#xff1a; 介绍Google推出的大一统模型—T5_谷歌大模型_深度之眼的博客-CSDN博客 T5 和 mT5-CSDN博客 T5&#xff1a;Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer&#xff08;万字长文略解T5&#xff09;_t5论文…

【原创创新点】金属工件缺陷检测系统:Efficient Multi-Scale-Conv的改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义&#xff1a;金属工件是现代工业生产中不可或缺的重要组成部分。金属工件的质量和性能直接影响到产品的品质和效率&#xff0c;因此对金属工件的研究和改进具有重要…

PyQt6 水平布局Horizontal Layout (QHBoxLayout)

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计41条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

邮件营销软件:10个创新邮件营销策略,提升投资回报率(一)

电子商务和电子邮件营销密不可分。尽管电子商务在蓬勃发展&#xff0c;而很多人对邮件营销颇有微词。但是在电子商务中&#xff0c;邮件营销的确是一种有效营销方式。在本文中&#xff0c;我们将讨论一下邮件营销在电子商务中的有效运用&#xff0c;帮助您的企业在今年尽可能地…

OpenAI在中国,申请GPT-6、GPT-7商标

根据最新商标信息显示&#xff0c;OpenAI已经在中国提交了GPT-6和GPT-7的商标注册信息&#xff0c;分类是科学仪器和网站服务两大类。申请日期是今年的11月2日&#xff0c;目前处于审核状态。 该申请由知识产权代理公司完成&#xff0c;但申请人的地址正是OpenAI在美国公司的地…

智能制造和低代码:打造高效工厂的关键

引言 随着全球制造业进入数字化时代&#xff0c;智能制造和低代码技术已经成为实现高效工厂运营的关键。这两个关键因素的融合为制造业带来了巨大的机会&#xff0c;使企业能够更灵活地应对市场需求、提高生产效率和降低成本。本文将深入探讨智能制造和低代码技术如何共同塑造…

解决electron修改主进程后需要重启才生效

nodemon 是一种工具&#xff0c;可在检测到目录中的文件更改时通过自动重新启动节点应用程序来帮助开发基于 node.js 的应用程序 nodemon 特性 自动重新启动应用程序。检测要监视的默认文件扩展名。默认支持 node&#xff0c;但易于运行任何可执行文件&#xff0c;如 python、…

拨号连接bat命令和拨号错误623,系统无法找到此连接的电话簿项的解决方法

一、拨号bat命令 1、首先创建一个拨号连接&#xff0c;注意连接名称要使用英文 2、创建一个bat文件&#xff0c;里面内容 echo off chcp 65001rem 定义连接参数&#xff0c;第一个是用户名&#xff0c;第二个是密码 set usernameS11111111111 set passwords11111111111 set…

m_map绘制多波束数据

之前&#xff0c;分享了m_map如何绘制本地高程数据&#xff1a;m_map导入本地地形数据。 其实&#xff0c;m_map还可以绘制多波束测深数据。 1. 多波束网站 多波束测深数据可以从https://www.ncei.noaa.gov/maps/grid-extract/或者https://www.ncei.noaa.gov/maps/bathymetr…

数据分析基础之《numpy(1)—介绍》

一、numpy介绍 1、numpy 数值计算库 num - numerical 数值化的 py - python 2、numpy是一个开源的python科学计算库&#xff0c;用于快速处理任意维度的数组 numpy支持常见的数组和矩阵操作。对于同样的数值计算任务&#xff0c;使用numpy比直接使用python要简洁的多 numpy使…

Linux系统调试课:网络性能工具总结

文章目录 一、网络性能指标二、netstat三、route四、iptables沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章一起了解下网络性能工具。 一、网络性能指标 从网络性能指标出发,你更容易把性能工具同系统工作原理关联起来,对性能问题有宏观的认识和把握。这样,…

MySql复习笔记03(小滴课堂) 事务,视图,触发器,存储过程

mysql 必备核心知识之事务的详细解析&#xff1a; 创建一个数据库表&#xff1a; 添加数据并开启事务。 添加数据并查询。 登录另一台服务器发现查不到这个表中的数据。 这是因为事务开启了&#xff0c;但是没有提交&#xff0c;只是把数据存到了内存中&#xff0c;还没有写入…

本地搭建Linux DataEase数据可视化分析工具并实现公网访问

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

飞天使-linux操作的一些技巧与知识点4

文章目录 ansible配置文件的优先级尝试开始进行操作ansible常用模块ansible 的playbook示例安装phpplaybook中变量的引用 ansible yum install -y ansible 测试是否可用 ansible localhost -m ping /etc/ansible/ansible.cfg &#xff1a;主配置文件&#xff0c;配置 ansible…