【数据结构】二叉搜索树的模拟实现

目录

1、概念

2、模拟实现

2.1、查找

2.2、插入

2.3、删除(难点)

3、性能分析

 4、完整代码


 

1、概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

Java底层实现搜索树的两个主要类是TreeSetTreeMap

        TreeSet是基于红黑树(Red-Black tree)实现的,它提供了对元素的唯一性排序。TreeSet中的元素是唯一的,并且按照升序排序。

        TreeMap也是基于红黑树实现的,它提供了一个键值对的映射关系,并且按照键的升序排序。TreeMap允许使用null键和null值。

        这两个类都实现了NavigableMap和SortedMap接口,提供了更丰富的方法用于搜索、排序和遍历操作。

        为了更好的理解TreeSet和TreeMap的使用以及底层原理,下面带大家模拟实现一下搜索树。

2、模拟实现

2.1、查找

 若根节点不为空:

  • 如果根节点key==查找key,返回true
  • 如果根节点key > 查找key,去其左子树上查找
  • 如果根节点key < 查找key,去其右子树上查找

否则返回false

    public boolean search(int val) {TreeNode cur = root;while (cur != null) {if (cur.val > val) {   //当前节点大于val,去左子树上继续找cur = cur.left;} else if (cur.val < val) {   //当前节点小于val,去右子树上继续找cur = cur.right;} else {return true;   //找到返回true}}return false;}

2.2、插入

插入时分为两种大情况:树为空以及树不为空

1、如果树为空树,即根 == null,直接插入

2、如果树不是空树,按照查找逻辑确定插入位置,插入新结点

    public void insert(int val) {if (root == null) {    //树为空时直接插入并返回root = new TreeNode(val);return;}TreeNode cur = root;TreeNode prev = null;    //指向cur前一个节点(即父亲节点),用于最终插入时使用while (cur != null) {prev = cur;      //更新prev指向if (cur.val > val) {cur = cur.left;} else if (cur.val < val) {cur = cur.right;} else {return;  //遇到相同退出,无需重复插入}}if (prev.val > val) {    //判断插入的正确位置prev.left = new TreeNode(val);} else {prev.right = new TreeNode(val);}}

2.3、删除(难点)

删除操作是二叉搜索树三种基本操作中最难的一个操作,它的难点不在于代码的难度,而是因为涉及到的情况居多,稍微不注意就容易漏判断某一条情况,因此使用合理的思路去理解记忆二叉搜索树的删除操作是非常必要的。

 设待删除结点为 cur, 待删除结点的双亲结点为 prev。对cur删除节点的三种情况进行分析:

1. cur.left == null

  • cur 是 root,则 root = cur.right
  • cur 不是 root,cur 是 prev.left,则 prev.left = cur.right
  • cur 不是 root,cur 是 prev.right,则 prev.right = cur.right

2. cur.right == null

  • cur 是 root,则 root = cur.left
  • cur 不是 root,cur 是 prev.left,则 prev.left = cur.left
  • cur 不是 root,cur 是 prev.right,则 prev.right = cur.left

3. cur.left != null && cur.right != null

需要使用替换法进行删除。

替换法的核心是:找到删除节点左子树中的最大值(即子树中的最右节点,这个节点一定没有右右子树),或者删除节点右子树中的最小值(即子树中的最左节点,这个节点一定没有左子树),用它的值填补到被删除节点中,再来处理该结点的删除问题。

以下讲解使用替换删除节点右子树的最小节点:

首先找到最小节点tmp,以及最小节点的父亲tmpPrev,如图所示结构,紧着cur.val = tmp.val替换值,然后删除tmp节点,删除步骤如图所示,即tmpPrev.left = tmp.right。

值得注意的是,有一种情况会被忽略,即当cur.right 节点无左子树时,此时tmpPrev仍然是cur,而tmp即为cur.right,此时删除tmp节点的步骤就变为了tmpPrev.right = tmp.right 

 

    public void remove(int val) {TreeNode cur = root;TreeNode prev = null;while (cur != null) {prev = cur;if(cur.val > val) {cur = cur.left;} else if (cur.val < val){cur = cur.right;} else {removeNode(cur,prev);return;}}}public void removeNode(TreeNode cur, TreeNode prev) {if(cur.left == null) {if(cur == root) {root = cur.right;} else if (prev.left == cur) {prev.left = cur.right;} else {prev.right = cur.right;}} else if (cur.right == null) {if(cur == root) {root = cur.left;} else if (prev.left == cur) {prev.left = cur.left;} else {prev.right = cur.left;}} else {TreeNode tmp = cur.right;TreeNode tmpPrev = cur;while(tmp.left != null) {tmpPrev = tmp;tmp = tmp.left;}cur.val = tmp.val;//注意,可能没有进入while循环,此时这里的两种情况if (tmpPrev.left == tmp) {tmpPrev.left = tmp.right;} else {tmpPrev.right = tmp.right;}}}

3、性能分析

  • 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
  • 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
  • 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

 

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:\log_{2}N

最差情况下,二叉搜索树退化为单支树,其平均比较次数为:\frac{N}{2}

如果退化成单支树,二叉搜索树的性能就失去了,而为了改进这一缺陷,就有了AVL树。

AVL树是最先发明的自平衡二叉搜索树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

本篇博客的重点不在AVL树上,因此不对AVL树进行展开讲解,可自行搜索了解。 

 4、完整代码

package BinarySearchTree;public class BinarySearchTree {static class TreeNode {public int val;public TreeNode left;public TreeNode right;public TreeNode(int val) {this.val = val;}}public TreeNode root;public boolean search(int val) {TreeNode cur = root;while (cur != null) {if (cur.val > val) {cur = cur.left;} else if (cur.val < val) {cur = cur.right;} else {return true;}}return false;}public void insert(int val) {if (root == null) {root = new TreeNode(val);return;}TreeNode cur = root;TreeNode prev = null;while (cur != null) {prev = cur;if (cur.val > val) {cur = cur.left;} else if (cur.val < val) {cur = cur.right;} else {return;  //相同退出}}if (prev.val > val) {prev.left = new TreeNode(val);} else {prev.right = new TreeNode(val);}}public void remove(int val) {TreeNode cur = root;TreeNode prev = null;while (cur != null) {prev = cur;if(cur.val > val) {cur = cur.left;} else if (cur.val < val){cur = cur.right;} else {removeNode(cur,prev);return;}}}public void removeNode(TreeNode cur, TreeNode prev) {if(cur.left == null) {if(cur == root) {root = cur.right;} else if (prev.left == cur) {prev.left = cur.right;} else {prev.right = cur.right;}} else if (cur.right == null) {if(cur == root) {root = cur.left;} else if (prev.left == cur) {prev.left = cur.left;} else {prev.right = cur.left;}} else {TreeNode tmp = cur.right;TreeNode tmpPrev = cur;while(tmp.left != null) {tmpPrev = tmp;tmp = tmp.left;}cur.val = tmp.val;//注意当没有进入while循环时这里的两种情况if (tmpPrev.left == tmp) {tmpPrev.left = tmp.right;} else {tmpPrev.right = tmp.right;}}}
}

 【博主推荐】

【LeetCode力扣】面试题 17.14. 最小K个数(top-k问题)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/135737266?spm=1001.2014.3001.5501【LeetCode力扣】42. 接雨水-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/134104222?spm=1001.2014.3001.5501【LeetCode力扣】189 53 轮转数组 | 最大子数组和-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/134095703?spm=1001.2014.3001.5501

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

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

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

相关文章

C语言实现基础数据结构——顺序表

目录 顺序表 顺序表和数组 顺序表的分类 静态顺序表 动态顺序表 静态顺序表和动态顺序表的比较 动态顺序表的实现 主要实现功能 顺序表的初始化 顺序表的销毁 顺序表的打印 顺序表的尾部插入 顺序表的头部插入 顺序表的尾部删除 顺序表的头部删除 顺序表的指定…

如何使用docker compose安装APITable并远程访问登录界面

文章目录 前言1. 部署APITable2. cpolar的安装和注册3. 配置APITable公网访问地址4. 固定APITable公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 …

Java基础常见面试题总结(下)

常见的Exception有哪些&#xff1f; 常见的RuntimeException&#xff1a; ClassCastException //类型转换异常IndexOutOfBoundsException //数组越界异常NullPointerException //空指针ArrayStoreException //数组存储异常NumberFormatException //数字格式化异常ArithmeticE…

Mysql-InnoDB-数据落盘

概念 1 什么是脏页&#xff1f; 对于数据库中页的修改操作&#xff0c;则首先修改在缓冲区中的页&#xff0c;缓冲区中的页与磁盘中的页数据不一致&#xff0c;所以称缓冲区中的页为脏页。 2 脏页什么时候写入磁盘&#xff1f; 脏页以一定的频率将脏页刷新到磁盘上。页从缓冲区…

教你一招,测试人员如何通过AI提高工作效率!

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

C++仿函数、万能头文件、transform学习

这是网上的一个代码,里面的一些东西以前没用过; #include <bits/stdc++.h> using namespace std;// A Functor class increment { private:int num; public:increment(int n) : num(n) { }int operator () (int arr_num) const {return num + arr_num;} };// Driver …

【破事水】Java Gradle 无法引入同名不同版本的两个包

此问题水于 2024 年 01 月&#xff0c;假如后面 gradle 出了什么好方法能解决这个问题&#xff0c;家祭无忘告乃翁&#xff0c;提前谢过看到这篇的各位大佬了。 结论 先说结论&#xff0c;Java 因为包名定义等原因&#xff0c;对同名包在编译时只能编译一个版本&#xff0c;具…

Kafka高级_生产者ACk机制数据一致性问题

Kafka高级_生产者ACk机制&数据一致性问题 目录需求&#xff1a; 设计思路实现思路分析1.Kafka高级_生产者ACk机制2.Kafka高级数据一致性问题 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c…

微信小程序开发学习笔记《13》WXS脚本

微信小程序开发学习笔记《13》WXS脚本 博主正在学习微信小程序开发&#xff0c;希望记录自己学习过程同时与广大网友共同学习讨论。建议仔细阅读对应官方文档 一、WXS介绍 WXS ( WeiXin Script)是小程序独有的一套脚本语言&#xff0c;结合WXML&#xff0c;可以构建出页面的…

CSDN COC·北京开发者社区2023年度聚会

CSDN COC北京开发者社区2023年度聚会 年末盛会&#xff1a;北京开发者社区2023年度聚会 1. 活动背景&#x1f389;2. 活动议程3. 活动亮点介绍 &#x1f31f;3.1 主理人开场破冰3.2 话题讨论&#xff1a;3.3 城市社区介绍、回顾与展望 &#x1f3d9;️4. 活动留念5.活动总结 博…

浅出深入-机器学习

文章目录 一、K近邻算法1.1 先画一个散列图1.2 使用K最近算法建模拟合数据1.3 进行预测1.4 K最近邻算法处理多元分类问题1.5 K最近邻算法用于回归分析1.6 K最近邻算法项目实战-酒的分类1.6.1 对数据进行分析1.6.2 生成训练数据集和测试数据集1.6.3 使用K最近邻算法对数据进行建…

计算机网络——路由信息协议 (RIP) 实验

1.实验题目 实验五&#xff1a;路由信息协议 (RIP) 实验 2.实验目的 &#xff08;1&#xff09;了解RIP的相关知识以及原理。 &#xff08;2&#xff09;掌握RIP路由的配置方法。 3.实验任务 (1) 路由器的基本配置&#xff1a;设置路由器接口 IP 地址。 (2) 根据以上拓扑…

Python Tornado 实现SSE服务端主动推送方案

一、SSE 服务端消息推送 SSE 是 Server-Sent Events 的简称&#xff0c; 是一种服务器端到客户端(浏览器)的单项消息推送。对应的浏览器端实现 Event Source 接口被制定为HTML5 的一部分。相比于 WebSocket&#xff0c;服务器端和客户端工作量都要小很多、简单很多&#xff0c…

[m1pro ] ssh: connect to host localhost port 22: Connection refused

在学习Hadoop 的时候&#xff0c;使用 ssh localhost 遇到以下问题 原因&#xff1a; 本地没有打开远程登录 解决办法&#xff1a;打开远程登录 成功结果

防御保护---防火墙的用户认证

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.用户认证概述 防火墙用户认证是一种安全措施&#xff0c;用于验证和授权网络用户的身份。它是防火墙的一部分&#xff0c;旨在确保只有经过身份验证的用户才能访问网络资源。 防火墙用户认证…

JDK1.8新特性(Day24)

Lambda表达式 介绍 Lambda表达式是一种没有名字的函数,也可称为闭包&#xff0c;是Java 8 发布的最重要新特性。本质上是一段匿名内部类&#xff0c;也可以是一段可以传递的代码。还有叫箭头函数的... 闭包 闭包就是能够读取其他函数内部变量的函数,比如在java中,方法内部的局…

智慧文旅:打造无缝旅游体验的关键

随着科技的快速发展和消费者需求的不断升级&#xff0c;旅游业正面临着前所未有的变革压力。智慧文旅作为数字化转型的重要领域&#xff0c;旨在通过智能化、数据化手段为游客提供更加优质、便捷、个性化的服务&#xff0c;打造无缝的旅游体验。本文将深入探讨智慧文旅在打造无…

第九节HarmonyOS 常用基础组件17-ScrollBar

1、描述 滚动条组件ScrollBar&#xff0c;用于配合可滚动组件使用&#xff0c;如List、Grid、Scroll。 2、接口 可包含子组件 ScrollBar(value:{scroller:Scroller, direction?: ScrollBarDirection, state?: BarState}) 3、参数 参数名 参数类型 必填 描述 scrolle…

R-YOLO

Abstract 提出了一个框架&#xff0c;名为R-YOLO&#xff0c;不需要在恶劣天气下进行注释。考虑到正常天气图像和不利天气图像之间的分布差距&#xff0c;我们的框架由图像翻译网络&#xff08;QTNet&#xff09;和特征校准网络&#xff08;FCNet&#xff09;组成&#xff0c;…

spire.doc合并word文档

文章目录 spire.doc合并word文档1. 引入maven依赖2. 需要合并的word3. 合并文档代码4. 合并结果5. 合并产生段落&#xff0c;table样式混乱问题 spire.doc合并word文档 1. 引入maven依赖 <repositories><repository><id>com.e-iceblue</id><name&g…