数据结构之二叉搜索树底层实现洞若观火!

目录

题外话

正题

二叉搜索树

底层实现

二叉搜索树查找操作

查找操作思路

查找代码实现详解

二叉搜索树插入操作

插入操作思路

插入代码详解

二叉搜索树删除操作

删除操作思路

删除代码详解

小结


题外话

我的一切都是党给的,都是人民给的,都是家人们给的!!

十分感谢家人们的大力支持,没有你们就没有我的今天(作者磕头三响!!!)

正题

今天来复习一下二叉搜索树正好完成一下底层实现

二叉搜索树

二叉搜索树概念:如果一颗二叉搜索树的任一结点的左子树不为空,那么左子树一定比该结点值小

                          如果一颗二叉搜索树的任一结点的右子树不为空,那么右子树一定比该结点值大

如下图,就是一颗二叉搜索树

任一一颗二叉搜索树中序遍历一定是有序的

底层实现

我们先将搜索二叉树的结点和结点值,还有左右子树创建出来

static  class TreeNode
{
    //创建结点元素值val
    public int val;


    //创建搜索二叉树的左右子树
    public TreeNode left;
    public TreeNode right;


    //通过TreeNode的构造方法赋值val
    public TreeNode(int val)
    {
        this.val=val;
    }

}
    //创建搜索二叉树的根
    public TreeNode root;

二叉搜索树查找操作

查找操作思路

我们通过搜索二叉树的概念,让key和根结点值比较

如果比根结点大,就和根结点右子树比较

如果比根结点小就和根结点左子树比较

例如上图,key等于9的时候在图中能找到9的结点

而当key等于10的时候,走到结点值为9的位置后左右结点为空,所以找不到等于10的结点返回flase

所以可以很容易写出以下代码

查找代码实现详解

public boolean search(int key)
{
    //将根结点赋值给cur,防止遍历完成找不到搜索二叉树的根节点
    TreeNode cur=root;


    //当cur不为空
    while (cur!=null)
    {


        //如果cur的val值小于key说明key在cur的右边
        if (cur.val<key)
        {
            //cur等于cur的右子树
           cur=cur.right;
        }
        //如果cur的val值比key大,说明key在cur的左边
        else if (cur.val>key)
        {
            //cur等于cur的左子树
            cur=cur.left;
        }
        //如果cur的val值等于key,说明找到了,则返回true
        else {
            return true;
        }
    }
    //如果cur为空还没有找到key说明key在搜索二叉树中不存在返回false
    return false;
}

二叉搜索树插入操作

插入操作思路

1.如果根结点为空,直接让插入元素val插入到根结点位置

2.如果根结点不为空

如果val大于根结点则继续判断root右子树和val大小关系

如果val小于根结点则继续判断root左子树和val大小关系

3.如下图所示,当我们走到结点值为9的位置发现val=10仍然大于9

然后继续往9的右子树走,发现为空

我们将val=10插入9的右子树,但是我们没有记录9这个结点,无法向9这个结点的右子树插入val

所以我们需要记录最近一次到达的上一个结点,以便可以将结点连接在一起

4.如果val是二叉搜索树中的结点值其中之一

比如val=5,这无法插入

因为二叉搜索树中的元素一定是没有重复的

要么比任意结点小,要么比任意结点大

不可能出现两个结点相同的情况

所以当出现插入元素和二叉搜索树中元素相同时,我们选择返回false

插入代码详解

public boolean insert(int val)
{
    //如果根结点为空则将插入结点作为根结点
    if (root==null)
    {
        root=new TreeNode(val);
        return true;
    }
    //如果根结点不为空,将root赋值给cur
    TreeNode cur=root;
    //创建prent记录cur到达的上一个结点
    TreeNode parent=null;
    //当cur不为空
    while (cur!=null) {
        //如果cur的val值比插入值小,则让parent记录当前cur结点,并且cur等于cur的右子树
        if (cur.val < val) {
            parent=cur;
            cur = cur.right;
        }
        //如果cur的val值比插入值大,则让parent记录当前cur结点,并且cur等于cur的左子树
        else if (cur.val > val) {
            parent=cur;
            cur = cur.left;
        }
        //如果cur的val值等于插入值,则无法插入,返回false
        else {
            return false;
        }
    }
    //当cur==null说明cur已经到达了要插入的位置
    TreeNode node=new TreeNode(val);
    //如果插入值比cur到达的上一个结点值大,则插入在上一个结点值的右边
    if (val> parent.val)
    {
        parent.right=node;
    }
    //如果插入值比cur到达的上一个结点值小,则插入在上一个结点值的左边
    else {
        parent.left=node;
    }
    //最后插入完成返回true
    return true;
}

接下来讲解一下搜索二叉树删除操作(有点难)

大家跟紧我的思维!

二叉搜索树删除操作

删除操作思路

首先咱们思考一下,删除搜索二叉树结点元素我们需要考虑什么问题

1.首先要遍历搜索二叉树找到要删除的结点

2.考虑搜索二叉树为空,没有结点的情况,无法删除

3.考虑搜索二叉树删除结点的左子树为空的情况如下图,cur为要删除结点

4.考虑搜索二叉树删除结点的右子树为空的情况,这里就不展示了,和上面情况差不多

5.考虑搜索二叉树删除结点的左子树右子树都不为空的情况(认真理解)

这里统一采用找到删除结点的右子树最深左结点的方法

找到get分两种情况下图是第一种

下图是第二种

以上内容我最开始也想不出来,但是做多了关于二叉树的题之后,也渐渐找到了这类型题的规律

基本上任何二叉树的题都和遍历二叉树有关系,也就是在遍历二叉树的基础上

无非是比较结点大小,或者结点是否存在等等变化,大家可以试着体会一下

删除代码详解

这里用了两个方法

1.第一个方法是找到要删除的结点

2.第二个方法是接收要删除的结点,以及遍历的上一个结点值

//这个方法是用来找到要删除的结点,并调用删除结点方法

public boolean remove(int val)
{
    //当二叉搜索树为空树,则返回false
    if (root==null)
    {
        return false;
    }
    //让root赋值给cur去遍历二叉搜索树
    TreeNode cur=root;
    //设置parent记录上一个遍历的结点
    TreeNode parent=null;
    //当cur不等于空就继续找删除结点
    while (cur!=null) {
        //如果cur的val值比删除值小,则让parent记录当前cur结点,并且cur等于cur的右子树
        if (cur.val < val) {
            parent=cur;
            cur = cur.right;
        }
        //如果cur的val值比删除值大,则让parent记录当前cur结点,并且cur等于cur的左子树
        else if (cur.val > val) {
            parent=cur;
            cur = cur.left;
        }
        //如果cur的val值等于删除值,则说明cur所在就是要删除的结点
        // 则调用remoNode方法,传入删除结点cur和parent
        else {
            removeNode(cur,parent);
            return true;
        }

    }
    //当cur等于空就说明二叉搜索树中不存在删除结点,返回false
    return false;
}

//这个方法负责接收要删除的结点和上一个到达的结点,并删除结点值

private void removeNode(TreeNode cur,TreeNode parent)
{
    //一.如果要删除结点的左子树为空(三种情况)
    if (cur.left==null)
    {
        //1.如果根结点等于要删除结点cur
        if (root==cur)
        {
            //让root指向root的右子树
            root=root.right;
        }
        //2.如果cur为parent的左子树
        else if (cur==parent.left)
        {
            //则让parent的左子树指向cur.right的右子树
            parent.left=cur.right;
        }
        //3.如果cur为parent的右子树
        else {
            //则让parent的右子树指向cur的右子树
            parent.right=cur.right;
        }
    }
    //二.当要删除结点cur的右子树为空时(三种情况)
    else if (cur.right==null)
    {
        //1.如果cur等于根结点
        if (cur==root)
        {
            //让根结点指向cur的左子树
            root=cur.left;
        }
        //2.如果cur等于parent的左子树
        else if(cur==parent.left)
        {
            //则让parent的左子树指向cur的左子树
            parent.left=cur.left;
        }
        //3.如果cur等于parent的右子树
        else {
           //则让parent的右子树指向cur的左子树
            parent.right=cur.left;
        }
    }
    //三.如果左子树右子树都不为空
    else {
        //设置getParent指向cur,设置get指向cur的右子树
        TreeNode getParent=cur;
        TreeNode get=cur.right;
        //如果get的左子树不为空,就去找到cur.right的最深左结点,并让getPatent记录上一个到达的结点
        while (get.left!=null)
        {
            getParent=get;
            get=get.left;
        }
        //当get左子树为空,说明找到了最深左结点,让get的val覆盖cur的val
        cur.val=get.val;
        //如果cur.right有左子树,getParent的左子树一定会等于get结点
        if (getParent.left==get)
        {
            //get不可能有左子树了,所以直接让getParent左子树指向get的右子树,图中第一种情况
            getParent.left=get.right;
        }
        //如果cur.right没有左子树,直接让getParent的右子树指向get的右子树即可,图中第二种情况
        else {
            getParent.right=get.right;
        }
    }
}

小结

今天是真的超级努力

早上从10点多起床,洗漱然后吃饭,从十一点半学到了现在,七个多小时,晚上还有课三个小时

最热爱的一集!!

麻烦喜欢的家人们给个三连好嘛!!!(点赞关注收藏!!!)

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

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

相关文章

Java分布式ID

1 什么是分布式ID 分布式ID是指在分布式系统中生成的唯一标识符&#xff0c;用于标识不同实体或数据的唯一性。在分布式系统中&#xff0c;多台机器并行处理任务&#xff0c;为了确保生成的ID在整个系统中的唯一性&#xff0c;需要采用特殊的算法来生成分布式ID。 在传统的单机…

Docker的数据管理、网络通信和dockerfile

目录 一、Docker的数据管理 1. 数据卷 1.1 数据卷定义 1.2 数据卷配置 2. 数据卷容器 2.1 创建数据卷容器 2.2 使用--volume-from来挂载test1 二、端口映射 三、容器互联 1. 创建容器互联 ​编辑2. 进入test2测试&#xff08;ping 容器名/别名&#xff09; 四、Dock…

Python的pytest框架(5)--测试标记(Markers)

该篇将循序渐进地详细拆解 pytest.mark 装饰器&#xff1a; 目录 一、概念 二、标记的基本结构与使用 三、标记在测试中的层次应用 四、标记的筛选与运行 五、标记与测试行为控制 六、标记与测试参数化 七、标记的注册与自定义 1、通过pytest.ini配置文件&#xff1a;…

SpringBoot钩子函数

在Java Spring Boot中&#xff0c;并没有直接称为“钩子函数”的概念&#xff0c;但你可以通过实现特定的接口、注解、事件监听或使用AOP&#xff08;面向切面编程&#xff09;来实现类似的功能。这些功能允许你在应用的特定点插入自定义逻辑&#xff0c;类似于钩子函数的作用。…

c++11详解

目录 1.列表初始化 2.声明 3.右值引用和移动语句 4. c11新的类功能 5. 可变参数模板 6.lambda表达式 7.包装器 8. 后言 1. 列表初始化 1.1 {}的初始化 (1) c98标准规定可以使用{}对数组以及结构体进行统一的列表初始化. struct Point {int _x;int _y; };int main() {in…

Python数据权限的管理通常涉及到几个关键组件:身份验证,、授权和访问控制。这通常是通过使用数据库、ORM(对象关系映射)框架、API框架和中间件

在Python中&#xff0c;数据权限的管理通常涉及到几个关键组件&#xff1a;身份验证&#xff0c;、授权和访问控制。这通常是通过使用数据库、ORM&#xff08;对象关系映射&#xff09;框架、API框架和中间件等技术来实现的。以下是一些建议的步骤和工具&#xff0c;用于在Pyth…

C语言面经

25.类型相同的两个指针之间不能进行的运算 指针主要用于存储变量的内存地址。对于同类型的指针变量之间&#xff0c;有一些规则&#xff1a; a. 小于运算&#xff08;<&#xff09;&#xff1a;指针间的小于比较是基于它们指向的内存地址。地址较小的指针在小于比较中被认为…

【Unity】shader中参数传递

1、前言 unity shader这个对于我来说是真的有点难&#xff0c;今天这篇文章主要还是总结下最近学习到的一些东西&#xff0c;避免过段时间忘记了&#xff0c;可能有不对&#xff0c;欢迎留言纠正。 2、参数传递的两种方式 2.1 语义传递 语义传递这个相对来说是简单的 shad…

Csharp_pta2_2

7-7 C# 1.12 区间找数 编写控制台应用程序&#xff0c;根据用户输入的a、b、c、d值&#xff08;均为正整数且a不大于b&#xff09;&#xff0c;输出在[a, b]区间中能被c整除&#xff0c;但是不能被d整除的数。 输入格式: 用户在一行中输入四个正整数&#xff0c;分别对应a、…

数组模拟几种基本的数据结构

文章目录 数组模拟单链表数组模拟双链表数组实现栈数组模拟队列总结 数组模拟单链表 首先类比结构体存储单链表&#xff0c;我们需要一个存放下一个节点下标的数组&#xff0c;还需要一个存储当前节点的值的数组&#xff0c;其次就是一个int类型的索引&#xff0c;这个索引指向…

Python 实现视频去抖动技术

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 视频去抖动是视频处理中的一项重要技术&#xff0c;它可以有效地减少视频中由于相机震动或手…

springSecurity简单直接说明

引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombo…

MyBatis处理SQL中的特殊字符

方式一&#xff1a;转义字符 如下案例&#xff1a; < 表示小于的转义字符 <!-- 在Mapper XML文件中定义SQL语句 --> <select id"selectById" resultMap"BaseResultMap">select *from userwhere id < #{id}; </select>方式二&am…

设计模式:依赖倒转原则(Dependency Inversion Principle,DIP)介绍

依赖倒转原则&#xff08;Dependency Inversion Principle&#xff0c;DIP&#xff09;是面向对象设计原则之一&#xff0c;它强调高层模块不应该依赖于底层模块&#xff0c;二者都应该依赖于抽象。同时&#xff0c;抽象不应该依赖于具体实现细节&#xff0c;具体实现细节应该依…

嵌入式开发学习--进程、线程

什么是进程 进程和程序的区别 概念 程序&#xff1a;编译好的可执行文件&#xff0c;存放在磁盘上的指令和数据的有序集合&#xff08;文件&#xff09;&#xff0c;程序是静态的&#xff0c;没有任何执行的概念。 进程&#xff1a;一个独立的可调度的任务&#xff0c;执行一…

高可靠性部署系列(3)--- ASG双机热备(HA)

高可靠性部署系列(3)--- ASG双机热备(HA) 前言网络拓扑设备选型网络规划组网需求配置思路操作步骤步骤 1 HA接口管理地址配置步骤 2 HA全局配置步骤 3 配置同步步骤 4 接口状态同步组创建结果验证前言 近期有读者留言:“因华为数通模拟器仅能支持USG6000V的防火墙,无法支…

东方博宜1009 - 数组逆序

题目描述 给你 nn 个整数&#xff0c;将其逆序输出。 输入 第一行一个整数 nn &#xff08;3 \le n \le 1003≤n≤100)代表数的个数。 第二行 nn 个整数&#xff08;空格隔开&#xff09;&#xff08;这些数在 0 \sim 10^60∼106 之间)。 输出 nn 个整数&#xff08;空格…

恶补《操作系统》3_1——王道学习笔记

3内存管理 3.1_1 内存的基础知识 1、什么是内存&#xff0c;作用 &#xff08;1&#xff09;内存&#xff1a;内存用来存放数据。程序执行前需要先放到内存中才能被CPU处理――缓和CPU与硬盘之间的速度矛盾。 &#xff08;2&#xff09;内存存储单元&#xff1a;每个地址对应…

AIGC技术的发展现状和未来趋势

AIGC&#xff08;人工智能生成内容&#xff09;技术是指利用人工智能算法自动生成文本、图像、音频、视频等各类内容的技术。随着深度学习等技术的快速发展&#xff0c;AIGC技术在最近几年取得了显著进步&#xff0c;并在多个领域展现出巨大的潜力。 ​ 编辑 发展现状&#x…

前端数字计算精度问题

计算精度问题通常发生在浮点数运算中&#xff0c;由于浮点数的表示所限&#xff0c;可能导致精度损失。 举例 // 比如 0.1 0.2 // 结果为 0.30000000000000004 0.3 - 0.1 // 结果为 0.19999999999999996vue vue 使用decimal.js 解决小数相加合计精确度丢失问题 微信小程序 …