数据结构与算法-二叉树

news2025/4/23 15:57:00

什么树
树是 n(n>=0)个有限集。n=0是空树,在n>1的非空树中有且仅有一个根节点作为树根,其他结构分散在根节点下形成一个个子树。各个子树互不相交。在实际的编码环节中,我们可以用链表和数组来模拟树结构。

为什么需要树结构
我们知道数组结构查询速度快,但增删慢;链表结构增删快,但查询慢。那么,有没有一种数据结构可以两者兼得,在查询快的基础上增删也快呢。答案是 >> 树结构。

什么是二叉树
二叉树是一种特殊的树结构,换句话说就是在普通树转换而来。定义为:二叉树只有一个根节点,二叉树子树都只有两个节点,必须满足左节点小于父节点,右节点大于父节点,而且子节点左边最大值不能大于该节点,子节点右边的最小值不能小于该节点。

为什么要研究二叉树
因为很多实际问题抽象出来就是二叉树的形式,而且普通树很容易抓换成二叉树;另一方面二叉树的数据结构和算法都较为简单,非常便于我们用于实际模拟树结构。

二叉树的存储方式
1、顺序储存
一般用数组模拟顺序储存,用数组下标来区分父子节点层级关系。
二叉树索引满足规定:
父索引 = (子索引 -1)/2
左子索引 = 2父索引+1
右子索引 = 2父索引+2

2、链式储存
一般用链表标识,链表每个节点中包含当前节点数据、左子节点、右子节点、有些还包含是否删除标识。为什么有些包含是否删除标识,因为二叉树链表的删除非常复杂。如果涉及到被删除节点有两个子节点就需要用右子节点或者右子节点最左节点进行替换,该操作还是有些复杂。

删除节点如果存在左右子节点的情况:
在这里插入图片描述

小试牛刀
我们用链表来模拟二叉树数据结构
1、创建二叉树工具类提供增删查中序打印方法

/**
 * 二叉树
 * @author senfel
 * @version 1.0
 * @date 2022/12/20 11:15
 */
@Data
@Slf4j
public class BinaryTree {


    @Data
    private class Node{
        /**
         * 数据
         */
        private int key;
        /**
         * 左节点
         */
        private Node leftNode;
        /**
         * 右节点
         */
        private Node rightNode;

        public Node() {

        }

        public Node(int key, Node leftNode, Node rightNode) {
            this.key = key;
            this.leftNode = leftNode;
            this.rightNode = rightNode;
        }
    }


    /**
     * 根节点
     */
    private Node root;




    /**
     * 查找
     * @param key
     * @author senfel
     * @date 2022/12/20 11:20
     * @return com.example.datademo.data.BinaryTree.Node
     */
    public Node find(int key){
        Node current = root;
        while (current != null){
            if(key < current.getKey()){
                current = current.getLeftNode();
            }else if(key > current.getKey()){
                current = current.getRightNode();
            }else{
                return  current;
            }
        }
        return null;
    }

    /**
     * 新增
     * @param key
     * @author senfel
     * @date 2022/12/20 11:27
     * @return java.lang.Boolean
     */
    public Boolean add(int key){
        //封装node
        Node node = new Node(key, null, null);
        //插入节点
        if(Objects.isNull(root)){
           root = node;
        }else{
            Node current = root;
            Node parentNode = null;
            while (current != null){
                if(key > current.getKey()){
                    //当前值大于节点值 找右子节点
                    parentNode = current;
                    current = current.getRightNode();
                    if(Objects.isNull(current)){
                        //右子节点不存在,直接写入
                        parentNode.rightNode = node;
                        return true;
                    }
                }else if(key < current.getKey()) {
                    //当前值小于节点值 找左子节点
                    parentNode = current;
                    current = current.getLeftNode();
                    if (Objects.isNull(current)) {
                        //左子节点为空 直接写入
                        parentNode.leftNode = node;
                        return true;
                    }
                }else {
                    //当前值等于当前节点不插入直接默认返回true
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 删除节点
     * @param key
     * @author senfel
     * @date 2022/12/20 13:49
     * @return java.lang.Boolean
     */
    public Boolean delete(int key){
        //找到需要删除的节点
        Node waitNode = root;
        Node parentNode = root;
        Boolean isLeftNode = false;
        while (waitNode != null && waitNode.getKey() != key){
            if(key > waitNode.getKey()){
                //被删除数据大于当前节点 取右子节点
                isLeftNode = false;
                parentNode = waitNode;
                waitNode = waitNode.getRightNode();
            }else if(key < waitNode.getKey()){
                //被删除数据小于当前节点 取左子节点
                isLeftNode = true;
                parentNode = waitNode;
                waitNode = waitNode.getLeftNode();
            }
            if(Objects.isNull(waitNode)){
                return false;
            }
        }
        //如果当前节点没有左右子节点 直接删除即可
        if(waitNode.getLeftNode() == null && waitNode.getRightNode() == null){
            if(waitNode == root){
                //被删除节点就是根节点 根节点直接制空
                this.root = null;
                return true;
            }
            if(isLeftNode){
                //被删除节点是父节点的左子节点 将父节点左子节点置空即可
                parentNode.setLeftNode(null);
                return true;
            }else{
                //将父节点右子节点置空即可
                parentNode.setRightNode(null);
            }
        }else if(waitNode.getLeftNode() != null && waitNode.getRightNode() == null){
            //左子节点不为空 右子节点为空
            if(waitNode == root){
                //被删除节点就是根节点 将被删除节点左子节点放在根节点位置
                this.root = parentNode.getLeftNode();
                return true;
            }
            if(isLeftNode){
                //被删除节点是父节点的左子节点
                parentNode.setLeftNode(waitNode.getLeftNode());
                return true;
            }else{
                parentNode.setRightNode(waitNode.getLeftNode());
                return true;
            }
        }else if(waitNode.getLeftNode() == null && waitNode.getRightNode() != null){
            //被删除节点左节点为空 右节点不为空
            if(waitNode == root){
                //将右节点升级为根节点
                this.root = waitNode.getRightNode();
                return true;
            }
            if(isLeftNode){
                //被删除节点是父节点的左节点 将节点的右节点升级为父节点左节点
                parentNode.setLeftNode(waitNode.getRightNode());
                return true;
            }else{
                parentNode.setRightNode(waitNode.getRightNode());
                return true;
            }
        }else{
            //左右子节点都不为空
            //1、先要获取替换节点,一般为被删除节点右节点,如果该节点存在左子节点则需要找到最小的左子节点
            Node updateNode = getUpdateNode(waitNode);
            if(updateNode == null){
                return false;
            }
            if(waitNode == root){
                this.root = updateNode;
                updateNode.setLeftNode(waitNode.getLeftNode());
                return true;
            }
            if(isLeftNode){
                parentNode.setLeftNode(updateNode);
                updateNode.setLeftNode(waitNode.getLeftNode());
                return true;
            }else{
                parentNode.setRightNode(updateNode);
                updateNode.setLeftNode(waitNode.getLeftNode());
                return true;
            }
        }
        return false;
    }


    /**
     * 获取替换节点
     * @param waitNode
     * @author senfel
     * @date 2022/12/20 14:15
     * @return Node
     */
    private Node getUpdateNode(Node waitNode) {
        if(waitNode == null){
            return null;
        }
        Node parentNode = waitNode;
        Node updateNode = waitNode;
        Node current = waitNode.getRightNode();
        while (current != null){
            parentNode = updateNode;
            updateNode = current;
            current = current.getLeftNode();
        }
        if(updateNode != waitNode.getRightNode()){
            //被替换节点不是右子节点需要移动位置
            //1、将被替换节点的右节点放置在其父节点的左节点上
            parentNode.setLeftNode(updateNode.getRightNode());
            //2、将被替换节点的右节点重置为被删除节点的右节点
            updateNode.setRightNode(waitNode.getRightNode());
        }
        return updateNode;
    }

    /**
     * 中序打印二叉树数据
     * @param node
     * @author senfel
     * @date 2022/12/20 15:31
     * @return void
     */
    public void centerRead(Node node,StringBuffer str){
        if(node != null){
            centerRead(node.getLeftNode(),str);
            str.append(node.getKey()+" ");
            centerRead(node.getRightNode(),str);
        }
    }
}

2、提供测试方法

 public static void main(String[] args) {
        BinaryTree binaryTree = new BinaryTree();
        binaryTree.add(100);
        binaryTree.add(90);
        binaryTree.add(120);
        binaryTree.add(80);
        binaryTree.add(96);
        binaryTree.add(93);
        binaryTree.add(99);
        binaryTree.add(92);
        binaryTree.add(94);
        StringBuffer str = new StringBuffer();
        binaryTree.centerRead(binaryTree.root,str);
        log.error("\n二叉树插入数据后的中序获取结构为:"+str.toString());
        binaryTree.delete(90);
        str = new StringBuffer();
        binaryTree.centerRead(binaryTree.root,str);
        log.error("\n二叉树删除90元素数据后的中序获取结构为:"+str.toString());
    }

3、查看测试结果

二叉树插入数据后的中序获取结构为:80 90 92 93 94 96 99 100 120
在这里插入图片描述

二叉树删除90元素数据后的中序获取结构为:80 92 93 94 96 99 100 120
在这里插入图片描述

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

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

相关文章

【计算机考研408】进程运行的流程

由程序段&#xff08;进程运行的程序的代码&#xff09;、相关数据段、和PCB&#xff08;进程存在的唯一标志&#xff09;三个部分构成了进程实体&#xff0c;也称作进程映像。 注&#xff1a;&#xff08;引入线程后&#xff09;进程只作为cpu外的系统资源的分配单元。 注&a…

DOM算法系列007-判定给定节点是否为空白节点

UID: 20221220141216 aliases: tags: source: cssclass: created: 2022-12-20 空白节点 什么是空白节点&#xff1f; 当一个节点的节点值为空文本值时&#xff0c;这个节点就是空白节点。 节点值&#xff1a; 即节点的 nodeValue 属性值&#xff1a; 实际上&#xff0c;节点值…

程序员必看:一款巨好用的免费简历“神器”(据说有了它,再也不发愁找工作啦!)

先说地址&#xff1a;https://cvmaker.greedyai.com/ 相比于前两年&#xff0c;今年大家的求职热情依然不减&#xff0c;但市场却泼了一盆“冰水”。 无论是从后台收到的留言&#xff0c;还是各种各样的新闻报道&#xff0c;以及今年的各大平台招聘数据来看&#xff0c;总结…

java多线程 下

目录 线程的生命周期 线程的同步 Synchronized的使用方法 同步机制中的锁 同步的范围 单例设计模式之懒汉式(线程安全) 线程的死锁问题 Lock(锁) synchronized 与 Lock 的对比 线程的通信 JDK5.0 新增线程创建方式 新增方式一&#xff1a;实现Callable接口 新增方式二…

利用LSTM识别显式篇章关系实战 可作为毕设

1.显式篇章关系分类概述 案例知识点: 任务描述:篇章关系分析是自然语言中处理篇章级基础语言分析任务,其目的是利用规则或机器学习等计算机处理手段判别篇章各组成成分之间的修辞逻辑关系,从而从整体上理解篇章。其中论元之间有连接词连接的此类关系称为显式篇章关系。本教…

RabbitMQ实战教程

RabbitMQ实战教程1.什么是RabbitMQ1.1 MQ&#xff08;Message Queue&#xff09;消息队列1.1.1 异步处理1.1.2 应用解耦1.1.3 流量削峰1.2 背景知识介绍1.2.1 AMQP高级消息队列协议1.2.2 JMS1.2.3 二者的联系1.2.4 Erlang语言1.3 为什么选择RabbitMQ1.4 RabbitMQ各组件功能2.怎…

【springboot 2.5.14 +jsp】打jar包,超详细,亲测可用,带源码

【springboot 2.5.14 jsp】打jar包&#xff0c;案例文档目录截图文件配置文件pom.xmlapplication.xmljspindex.jspjavaSpringbootJspApplication.javaHelloController.java打包方式运行源码地址文档目录截图 文件 配置文件 pom.xml <?xml version"1.0" encodi…

VEML6075的驱动代码

VEML6075的驱动代码VEML6075简介VEML6075相关参数VEML6075IIC读写相关时序VEML6075IIC读写驱动代码VEML6075IIC读写串口打印总结VEML6075简介 VEML6075是一种紫外线&#xff08;UV&#xff09;光传感器&#xff0c;它可以测量紫外线强度。它通常用于各种应用&#xff0c;包括环…

【云原生 | Kubernetes 实战】15、K8s 控制器 Daemonset 入门到企业实战应用

目录 一、DaemonSet 控制器&#xff1a;概念、原理解读 1.1 DaemonSet 概述 1.2 DaemonSet 工作原理&#xff1a;如何管理 Pod &#xff1f; 1.3 Daemonset 典型的应用场景 1.4 DaemonSet 与 Deployment 的区别 二、DaemonSet 资源清单文件编写技巧 三、DaemonSet …

零基础如何自学Python编程?

零基础如何系统地自学Python编程&#xff1f;绝大多数零基础转行者学习编程的目的就是想找一份高薪有发展前景的工作&#xff0c;哪个编程语言就业前景好越值得学习。零基础的同学学Python是一个不错的选择。 对于零基础的初学者最迷茫的是不知道怎样开始学习&#xff0c;建议…

基础背包问题--0 1背包与完全背包

&#x1f389;&#x1f389;&#x1f389;写在前面&#xff1a; 博主主页&#xff1a;&#x1f339;&#x1f339;&#x1f339;戳一戳&#xff0c;欢迎大佬指点&#xff01; 目标梦想&#xff1a;进大厂&#xff0c;立志成为一个牛掰的Java程序猿&#xff0c;虽然现在还是一个…

JS基于base64编码加密解密文本和图片

JS基于base64编码加密解密文本和图片 ​ 密码学&#xff0c;体系太庞大了&#xff0c;常见的加密解密算法很多&#xff0c;我仅了解了一下&#xff0c;这里仅介绍采用实现base64加密解密的方法。 严格地说base64不是加密算法&#xff0c;他只是一种编码方式&#xff0c;是一…

企业经营管理的核心是什么?

一、企业经营管理是什么&#xff1f; 企业经营管理通常是指&#xff0c;企业为了满足自身生存发展&#xff0c;通过对企业内部成员的经营活动进行计划、组织、协调、指挥、控制。企业经营管理主要目的是为了让企业在面向市场和用户是时&#xff0c;可以充分利用企业自身优势和…

excel日期函数:如何计算项目的开始和完成日期

制定工作计划是我们平时工作中经常会遇到的一类事务&#xff0c;例如某个项目&#xff0c;需要分成七个阶段来完成&#xff0c;已知项目的开始日期和每个项目需要的时间&#xff08;以天为单位&#xff09;&#xff0c;就可以做出一个项目的工作计划表&#xff1a; 需要重点强调…

无约束优化:修正阻尼牛顿法

文章目录无约束优化&#xff1a;修正阻尼牛顿法梯度法的困难经典牛顿法定义收敛性证明修正阻尼牛顿法考虑修正阻尼牛顿法的起因如何构造修正矩阵M参考文献无约束优化&#xff1a;修正阻尼牛顿法 梯度法的困难 无约束优化&#xff1a;线搜索最速下降 对于光滑函数而言&#x…

pg 锁机制深析

spin lock 使用 cas 去获取锁&#xff0c;先获取 spins_per_delay 次数&#xff0c;如果还失败&#xff0c;则每次获取失败将 delay 时长延长至 1~2倍 delay 值加 0.5 us&#xff0c;spins_per_delay 的值在获取锁后会做更新&#xff0c;如果这次没有等待&#xff0c;则下次可…

Python可视化——matplotlib.pyplot绘图的基本参数详解

目录 1.matplotlib简介 2.图形组成元素的函数用法 2.1. figure()&#xff1a;背景颜色 2.2 xlim()和 ylim()&#xff1a;设置 x&#xff0c;y 轴的数值显示范围 2.3 xlabel()和 ylabel()&#xff1a;设置 x&#xff0c;y 轴的标签文本 2.4 grid()&#xff1a;绘制刻度线的…

NVIDIA深度学习基础-理论与实践入门课程笔记及测验参考代码

1. 使用MNIST数据集进行图像分类 1.1 MNIST数据集 在深度学习的历史当中,对 MNIST 数据集里的70000张手写体数字的图像进行0到9的正确分类是一个重大的进展。如今,这个问题被认为是微不足道的,但是使用 MNIST 进行图像分类已经成为深度学习的一个 Hello World 练习。 以下…

TDC-GP30固件升级笔记

Bootloader介绍 系统重置或系统INIT发生后&#xff0c;总是请求引导加载程序。但是&#xff0c;只有在设置了引导加载器发布代码时&#xff0c;才会执行引导加载器操作。 Bootloader操作包括&#xff1a; “Register Configuration” 寄存器配置”&#xff0c;将配置数据传输…

8-Arm PEG-DBCO分子量决定外观性状,用于修饰生物分子

英文名称&#xff1a;8-Arm PEG-DBCO 中文名称&#xff1a;八臂-聚乙二醇-二苯基环辛炔 分子量&#xff1a;1k&#xff0c;2k&#xff0c;3.4k&#xff0c;5k&#xff0c;10k&#xff0c;20k&#xff08;可按需定制&#xff09; 质量控制&#xff1a;95% 存储条件&#xff…