【数据结构】详解二叉搜索树及其实现

2024-09-04 17:36

本文主要是介绍【数据结构】详解二叉搜索树及其实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:

二叉搜索树是红黑树等的前身,掌握其操作和性质很重要。总结自用and分享。


目录

一、基本概念

二、其常见操作及其实现

1.定义节点

2.查找元素 

 3.插入元素

4.删除元素【难点】

 三、性质分析


一、基本概念

        如下所示:对于所有节点都满足该性质:子树上所有节点的值都小于该节点的值。对于每个节点,右子树上所有节点的值都大于该节点的值。每个节点的左子树和右子树也是一个二叉搜索树。

其也叫二叉排序树,对其进行中序遍历可以得到排好序的序列。

二、其常见操作及其实现

1.定义节点

public class BinarySearchTree {private TreeNode root = null;//根节点// 定义节点class TreeNode {int val;TreeNode left;TreeNode right;public TreeNode(int val) {this.val = val;}
}

2.查找元素 

思想:

运用一个递归的思想:

1.先看看根节点是不是该元素,是的话,直接当前节点。

2.若不是,判断其位置,若该元素比根大,那就去右子树找(跳到1)。

3.若该元素比根小,那就去左子树找(跳到1)。

4.若遍历完所有节点都找不到,返回null。

代码实现:

public TreeNode search(int val) {TreeNode curRoot = root;//当前的双亲节点while (curRoot != null) {if (curRoot.val == val) {return curRoot;} else if (curRoot.val > val) {curRoot = curRoot.left;} else {curRoot = curRoot.right;}}return null;}

 3.插入元素

注意:二叉树的插入是不会破坏原有的结构的,插入元素,总是能找到合适的叶子节点插入,你们可以画图感受一下。

思想:

1.若该树为空,直接插入。

2.若树不为空:根据二叉树的性质,找到它的容身之处的双亲节点。

3.找到双亲节点之后,若比双亲节点大,插入其右边,若比双亲节点小,插入其左边。

代码实现:

    // 2.插入元素public boolean insert(int val) {// 如果根节点为空,直接插入,返回trueif (root == null) {root = new TreeNode(val);return true;}TreeNode cur = root;TreeNode parent = null;while (cur != null) {if (val == cur.val) {System.out.println("值为" + val + "的节点已经存在");return false;} else if (val > cur.val) {parent = cur;cur = cur.right;} else {parent = cur;cur = cur.left;}}// 抵达该位置时,已经找到合适的插入位置的双亲节点:parent// 要判断一下插入哪一边if (val > parent.val) {parent.right = new TreeNode(val);} else {parent.left = new TreeNode(val);}return true;}

4.删除元素【难点】

注意:刚说的插入元素不会破坏原有的树结构,但是删除元素就可能不得不破坏原有的树结构了,毕竟要把某个在中间的元素搬走...

a:初步思路:

    先找到该节点和其双亲节点,若找不到该节点,返回false。若找到了该节点,再根据情况进行删除调整。

 public boolean remove(int val) {TreeNode cur = root;TreeNode parent = null;while (cur != null) {if (cur.val < val) {parent = cur;cur = cur.right;} else if (cur.val > val) {parent = cur;cur = cur.left;} else {//找到要删除的元素和其双亲节点了removeNode(parent, cur);return true;}}System.out.println("删除失败,没找到值为"+val+"的节点");return false;}

 b:删除:(难点)

  1. 删除叶子节点:直接删除。
  2. 删除只有一边子节点的节点:用子节点替代该节点。(可涵盖情况1)
  3. 删除有两个子节点的节点【难点所在】:找到该节点的中序后继节点,用它的值替换被删除节点的值,然后删除该中序后继节点,并把该中序后继节点的子节点继承过去。

 再捋一下删除的情况:

     情况1、2: 我们找到了要删除的节点和其双亲节点。上述的情况2是可以包含情况1的:叶子节点也可以视为只有一边子节点(实际是null)的节点,然后子节点(实际是null),替代原本的节点。但是有一种情况我们要特殊处理,那就是要删除的节点是根节点时(此时对parent修改,只是流动了parent,影响不了root),我们需要直接拿到root去修改指向。

      情况3: 删除有两个子节点的节点【难点所在】

      找到要删除节点的右子树的最左节点,我们因为是最左节点,该节点肯定没有左子树了。

        我们把最左节点的值直接覆盖到要删除的节点,原因:最左节点是要删除节点的右子树里最小的,把它放在要删除的节点处,既可以满足,右子树中所有元素比节点大,也可以满足本来左子树中所有元素比节点小。(天然,右子树中任一元素大于左子树中的)

        善后阶段:最左节点的值已经被拿去,应该要删掉。这时候就回到情况1、2的问题了,且最左节点肯定不是头结点,处理起来也更简单。

private void removeNode(TreeNode parent, TreeNode cur) {// 情况1:要删除的节点没有左子节点if (cur.left == null) {// 如果要删除的节点是根节点,则直接将根节点指向其右子节点if (cur == root) {root = cur.right;} else if (cur == parent.left) {// 如果要删除的节点是其父节点的左子节点,则更新父节点的左指针parent.left = cur.right;} else {// 如果要删除的节点是其父节点的右子节点,则更新父节点的右指针parent.right = cur.right;}}// 情况2:要删除的节点没有右子节点else if (cur.right == null) {// 如果要删除的节点是根节点,则直接将根节点指向其左子节点if (cur == root) {root = cur.left;} else if (cur == parent.left) {// 如果要删除的节点是其父节点的左子节点,则更新父节点的左指针parent.left = cur.left;} else {// 如果要删除的节点是其父节点的右子节点,则更新父节点的右指针parent.right = cur.left;}}// 情况3:要删除的节点有左右子节点else {// 找到要删除节点的右子树中的最左(最小)节点TreeNode t = cur.right;TreeNode tp = cur;while (t.left != null) {tp = t;t = t.left;}// 将最左节点的值赋值给要删除的节点cur.val = t.val;// 删除最左节点,更新其父节点的左指针或右指针if (tp.left == t) {tp.left = t.right;} else {tp.right = t.right;}}}

 三、性质分析

        由于二叉搜索树的特点,平均情况下,插入、查找和删除操作的时间复杂度都是 O(log⁡n)。但是,如果二叉搜索树退化成一条链表(例如插入有序数据),最坏情况下,操作时间复杂度会降到 O(n)。为了避免这种情况,实际应用中常使用平衡二叉搜索树(如AVL树、红黑树等)。

这篇关于【数据结构】详解二叉搜索树及其实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1136578

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函