【数据结构专栏】二叉搜索树(Binary Search Tree)的剖析?

在这里插入图片描述

文章目录

  • 🧨前言
    • 1、二叉搜索树的基本概念?
    • 2、二叉搜索树的节点结构组成?
    • 3、二叉搜索树的插入操作?
    • 4、二叉搜索树的删除操作?
    • 5、二叉搜索树的遍历?
    • 6、二叉搜索树的性能分析?
    • 🎉完整代码如下:

🧨前言

该章节主要就是明确二叉树是什么?二叉树的基本操作等。

1、二叉搜索树的基本概念?

二叉所搜索树:它是一棵有一定规则的二叉树。它的每个节点的都遵循这两个条件:
①左子树不为空的时候,左子树的所有节点的值都小于当前节点的值。
②右子树不为空的时候,右子树的所有节点的值都大于当前节点的值。
③左右子树也必须是二叉搜索树。

二叉搜索树的性质决定了节点之间的连接方式,也就是说它可能会连接长满二叉树,亦可能为完全二叉树,但是在插入的而数据是有序的时候,那连接形式就会成一棵链表,会导致该树的高度非常高,影响操作效率。但是有一种平衡二叉搜索树,比如AVL树或红黑树,可以解决这个问题,但这里先不考虑平衡,先假设是一棵普通的二叉搜索树进行讲述。

2、二叉搜索树的节点结构组成?

⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,map/set/multimap/multiset系列容器底层是⼆叉搜索树,其中map/set不⽀持插⼊相等
值,multimap/multiset⽀持插⼊相等值。
此文章的前提是使用一个普通二叉搜索树来分析,同时不包含重复值的节点
该树的节点组成有:
_key:用于存储数据值的变量。
_left:指向左子树的变量
_right:指向右子树的变量

代码结构如下:

template<class K>
class BSTNode
{
public://构造函数,初始化列表给变量赋值BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}
public:K _key;BSTNode<K>* _left;BSTNode<K>* _right;
};

3、二叉搜索树的插入操作?

插入‌:如果树为空,新节点成为根节点;否则,按照二叉搜索树的性质找到插入位置并插入新节点‌。
从根节点开始,比较目标值与当前节点的值:
若目标值较小,则移动到左子树;
若目标值较大,则移动到右子树。
重复上述过程,直到找到一个空位置插入新节点。
代码加注释讲解:

//插入数据函数。
bool Insert(const K& key)
{//首先,为空的时候,直接插入到根节点。if (_root == nullptr){_root = new Node(key);return true;}//根节点不为空的时候,那就先从根节点开始。Node* cur = _root;Node* parent = nullptr;//直到为空就插入while (cur){//若插入的数据值 小于 当前节点的数据值,往左走。if (key < cur->_key){//临时记录当前节点的父节点,不然后面链接不上新节点。parent = cur;cur = cur->_left;}//插入的数据值 大于 当前节点的数据值,往右走。else if (key > cur->_key){parent = cur;cur = cur->_right;}else{//走到此处说明找到一个空位置,跳出循环进行链接。return false;}}//找到cur==nullptr,此时parent节点就是最后一个节点,在插入key时候,//还需要判断一次,看插入的值比父节点值大,那么就当作右孩子,反之就当作左孩子。//由于此时cur是为空的,没必要另开空间来存储新节点,因此就把key放入该节点中cur = new Node(key);if (key < parent->_key){//插入到左边parent->_left = cur;}else{//插入到右边parent->_right = cur;}return true;
}

4、二叉搜索树的删除操作?

在实现二叉搜索树删除的接口的时候先是来说一下查找函数。
二叉搜索树的查找:思路和插入的一样,只是需要比较一下大小。

①通过需要查找的值key和二叉搜索树里面的每个节点的值cur->_key进行比较。查找值key 大于 当前节点值cur->_key,则就往当前节点cur的右子树走。
②查找值key 小于 当前节点值cur->_key,则就往当前节点cur的左子树走。
③找到后就返回当前节点cur

代码如下:

	//查找函数bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){//往左走cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return cur;//在判断是否存在某个值的时候,直接返回true即可。}}return false;//说明没找到。}

删除操作:

  1. 要删除结点N左右孩⼦均为空。即叶子节点,可以直接删除。
  2. 要删除的结点N左孩⼦位空右孩⼦结点不为空
  3. 要删除的结点N右孩⼦位空左孩⼦结点不为空
  4. 要删除的结点N左右孩⼦结点均不为空

对应序号的解决策略:

  1. 把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)。
  2. 把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点。
  3. 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点。
  4. ⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤替换法删除。

第4种情况替换如下:
①先找到右子树的最小值节点(或左子树的最大值节点)。
②再用该最小值(或最大值)替换被删除节点的值。
③最后记得删除找到的最小值(或最大值)节点。

	//删除函数bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;//先查找到对应删除的节点while (cur){if (cur->_key < key)//若当前节点的数据小于要比较的数据元素,则向右走{parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{//找到节点直接后就可以删除该节点//主要分为四种情况//1、左右都为空;2、左位空,右不为空;3、右位空,左不为空;4、左右都不为空// 左右为空的都可以划分到其中一个为空的情况,因为当左右都为空的时候,//下面检测到左节点为空,则就会进入左为空的程序//左为空if (cur->_left == nullptr){//先判断是否为根节点if (cur == _root){//直接把节点给到根节点_root = cur->_right;}else if (cur == parent->_left)//若是父节点的左孩子为空,则//把cur->_right给到父节点的左孩子{parent->_left = cur->_right;}else//反之就是把父节点的右孩子{parent->_right = cur->_right;}delete cur;}//右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}//左右都不为空else{//替代法Node* replaceParent = cur;//在删除左右都不为空的时候,需要找一个节点的值来替代,可以//是当前节点的右子树的最左节点,也可以是当前节点左子树的最右节点,//下面是用的右子树的最左节点。Node* replace = cur->_right;while (replace->_left){replaceParent = replace;replace = replace->_left;}//此处replace就是右子树的最左孩子节点//把值给到要删除的节点,即替换cur->_key = replace->_key;//此时需要判断replace节点是replaceParent节点的左孩子还是右孩子if (replace == replaceParent->_left){replaceParent->_left = replace->_right;}else{replaceParent->_right = replace->_right;}delete replace;//相当于替换后,就删除替换的那个节点}return true;}}//没找到就返回falsereturn false;}

5、二叉搜索树的遍历?

中序遍历:对于二叉搜索树,中序遍历会按升序输出所有节点的值。
前序遍历、后序遍历:按照特定顺序访问节点,但不保证有序性。

下面把三种遍历方式都写出来:

//中序遍历搜索二叉树
void _InOrder(Node* root)
{if (root == nullptr){return;}//左 根 右_InOrder(root->_left);cout << root->_key<<" ";_InOrder(root->_right);
}void _PrevOrder(Node* root)
{if (root == nullptr){return;}cout << root->_key << " ";_InOrder(root->_left);_InOrder(root->_right);
}void _PostOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);_InOrder(root->_right);	cout << root->_key << " ";
}

运行结果如下:
在这里插入图片描述

6、二叉搜索树的性能分析?

最好和平均情况:树的高度为O(log n),插入、删除和查找操作的时间复杂度为O(log n)

最坏情况:树的高度为O(n),时间复杂度退化为O(n),通常发生在插入有序数据时,树退化为链表

为避免性能下降,通常使用平衡二叉搜索树,如AVL树或红黑树,以保证树的高度始终接近O(log n)。后续文章会讲述。

总结:二叉搜索树是一种高效的数据结构,适用于需要频繁进行插入、删除和查找操作的场景。理解其基本操作和实现细节是掌握更复杂数据结构和算法的基础。在实际应用中,通常需要结合平衡技术来保证性能的稳定性。

🎉完整代码如下:

#pragma once#include<iostream>
using namespace std;namespace N
{//先创建二叉树结构template<class K>class BSTNode{public://构造函数BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}public:K _key;BSTNode<K>* _left;BSTNode<K>* _right;};template<class K>class BSTree{typedef BSTNode<K> Node;public://插入数据函数bool Insert(const K& key){//首先,为空的时候,直接插入到根节点if (_root == nullptr){_root = new Node(key);return true;}//根节点不为空的时候,那就先从根节点开始Node* cur = _root;Node* parent = nullptr;//直到为空就插入while (cur){//若插入的数据小于节点的数据值,往左走if (key < cur->_key){//记录下一节点的父节点parent = cur;cur = cur->_left;}//大于节点的值else if (key > cur->_key){parent = cur;//往右边走cur = cur->_right;}//相等就返回else{return false;}}//找到cur==nullptr,此时parent节点就是最后一个节点,在插入key时候,//还需要判断一次,比父节点值大,就插入到右孩子处,反之就左孩子//由于cur是为空的,因此把key放入该节点中cur = new Node(key);if (key < parent->_key){//插入到左边parent->_left = cur;}else{//插入到右边parent->_right = cur;}return true;}//查找函数bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){//往左走cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return true;}}return false;}//删除函数bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;//先查找到对应删除的节点while (cur){if (cur->_key < key)//若当前节点的数据小于要比较的数据元素,则向右走{parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{//找到节点直接后就可以删除该节点//主要分为四种情况//1、左右都为空;2、左位空,右不为空;3、右位空,左不为空;4、左右都不为空// 左右为空的都可以划分到其中一个为空的情况,因为当左右都为空的时候,//下面检测到左节点为空,则就会进入左为空的程序//左为空if (cur->_left == nullptr){//先判断是否为根节点if (cur == _root){//直接把节点给到根节点_root = cur->_right;}else if (cur == parent->_left)//若是父节点的左孩子为空,//则把cur->_right给到父节点的左孩子{parent->_left = cur->_right;}else//反之就是把父节点的右孩子{parent->_right = cur->_right;}delete cur;}//右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}//左右都不为空else{//替代法Node* replaceParent = cur;//在删除左右都不为空的时候,需要找一个节点的值来替代,//可以是当前节点的右子树的最左节点Node* replace = cur->_right;while (replace->_left){replaceParent = replace;replace = replace->_left;}//此处replace就是右子树的最左孩子节点//把值给到要删除的节点,即替换cur->_key = replace->_key;//此时需要判断replace节点是replaceParent节点的左孩子还是右孩子if (replace == replaceParent->_left){replaceParent->_left = replace->_right;}else{replaceParent->_right = replace->_right;}delete replace;//相当于替换后,就删除替换的那个节点}return true;}}//没找到就返回falsereturn false;}void InOrder(){_InOrder(_root);}void PrevOrder(){_PrevOrder(_root);}void PostOrder(){_PostOrder(_root);}private://中序遍历搜索二叉树void _InOrder(Node* root){if (root == nullptr){return;}//左 根 右_InOrder(root->_left);cout << root->_key<<" ";_InOrder(root->_right);}void _PrevOrder(Node* root){if (root == nullptr){return;}cout << root->_key << " ";_InOrder(root->_left);_InOrder(root->_right);}void _PostOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);_InOrder(root->_right);	cout << root->_key << " ";}private://BSTNode<K>* _root=nullptr;//定义根节点Node* _root = nullptr;};
}

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

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

相关文章

分布式调用 - 服务间的远程调用RPC

文章目录 导图PreRPC 概述RPC 调用过程RPC 动态代理1. 接口定义 (SeverProvider)2. 实现类 (ServerProviderImpl)3. 动态代理类 (DynamicProxy)4. 客户端 (Client)5. 代码工作流程6. 总结和注意点7. 结果输出8. 小结 RPC 序列化协议编码网络传输 导图 服务和应用的调用基于场景…

vue3项目搭建-4-正式启动项目,git管理

安装插件&#xff1a; npm install vue router npm install eslint 完成目录&#xff1a; 需要添置文件夹&#xff1a; apis -> api接口 composables -> 组合函数 directives -> 全局指令 styles -> 全局样式 utils -> 工具函数 git 管理&#xff1a; …

GPON原理

GPON网络架构 对于OLT来说&#xff0c;它就相当于一个指挥官&#xff0c;它指挥PON口下的ONU在指定的时间段内发送数据以及发起测距过程等 而ONU则是一个士兵&#xff0c;按照OLT的指挥做出相应 而ODN它主要就是提供一个传输通道&#xff0c;主要包括分光器和光纤组成 对于PO…

SJYP 24冬季系列 FROZEN CHARISMA发布

近日&#xff0c;女装品牌SJYP 2024年冬季系列——FROZEN CHARISMA已正式发布&#xff0c;展现了更加干练的法式风格。此次新品发布不仅延续了SJYP一贯的强烈设计风格和个性时尚&#xff0c;更融入了法式风情的干练元素&#xff0c;为消费者带来了一场视觉与穿着的双重盛宴。  …

【H2O2|全栈】JS进阶知识(十一)axios入门

目录 前言 开篇语 准备工作 获取 介绍 使用 结束语 前言 开篇语 本系列博客主要分享JavaScript的进阶语法知识&#xff0c;本期主要对axios进行基本的了解。 与基础部分的语法相比&#xff0c;ES6的语法进行了一些更加严谨的约束和优化&#xff0c;因此&#xff0c;在…

git使用文档手册

创建一个本地代码工作空间&#xff0c;比如这里使用test目录作为工作目录 针对仓库地址 http://192.168.31.125:9557/poxiaoai-crm/project-crm.git。 1. 安装 Git 确保您的系统已经安装了 Git。如果未安装&#xff0c;请根据操作系统访问 Git 官网 下载并安装。 验证安装 …

数据结构——排序算法第二幕(交换排序:冒泡排序、快速排序(三种版本) 归并排序:归并排序(分治))超详细!!!!

文章目录 前言一、交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare版本 快排1.2.2 挖坑法 快排1.2.3 lomuto前后指针 快排 二、归并排序总结 前言 继上篇学习了排序的前面两个部分:直接插入排序和选择排序 今天我们来学习排序中常用的交换排序以及非常稳定的归并排序 快排可是有多…

11.26深度学习_神经网络-数据处理

一、深度学习概述 1. 什么是深度学习 ​ 人工智能、机器学习和深度学习之间的关系&#xff1a; ​ 机器学习是实现人工智能的一种途径&#xff0c;深度学习是机器学习的子集&#xff0c;区别如下&#xff1a; ​ 传统机器学习算法依赖人工设计特征、提取特征&#xff0c;而深…

数据结构 (13)串的应用举例

前言 数据结构中的串&#xff08;String&#xff09;&#xff0c;也称为字符串&#xff0c;是一种常见且重要的数据结构&#xff0c;在计算机科学中被广泛应用于各种场景。 一、文本处理 文本编辑器&#xff1a;在文本编辑器中&#xff0c;字符串被用来表示和存储用户输入的文本…

PointNet++论文复现

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

时间的礼物:如何珍视每一刻

《时间的礼物&#xff1a;如何珍视每一刻》 夫时间者&#xff0c;宇宙之精髓&#xff0c;生命之经纬&#xff0c;悄无声息而流转不息&#xff0c;如织锦之细线&#xff0c;串联古今&#xff0c;贯穿万物。 人生短暂&#xff0c;犹如白驹过隙&#xff0c;倏忽而逝&#xff0c;…

NVIDIA /CUDA 里面的clock rate详细介绍

本文主要介绍&#xff1a; cuda中的时钟频率具体有哪些&#xff1f;clock rate怎么调节&#xff1f; cuda中可以通过nvml 函数或者命令来调整时钟频率&#xff08;clock rate&#xff09; 介绍 命令行 nvdia-smi -q -i 0 可以查询device相关参数&#xff0c;可以用后面的命…

扫振牙刷设计思路以及技术解析

市面上目前常见的就两种&#xff1a;扫振牙刷和超声波牙刷 为了防水&#xff0c;表面还涂上了一层防水漆 一开始的电池管理芯片&#xff0c;可以让充电更加均衡。 如TP4056 第一阶段以恒流充电&#xff1b;当电压达到预定值时转入第二阶段进行恒压充电&#xff0c;此时电流逐…

电磁继电器

它的控制原理很简单&#xff0c;当我们给它的线圈接电&#xff0c;这个线圈就有了磁性&#xff0c;它上面的衔铁就会被吸引&#xff0c;这样小灯泡就会点亮 继电器于MOS管的差别在于&#xff0c;继电器可以很轻松的胜任高电压、大电流的场合 我们从外壳上可以看到 30VDC&#x…

【Jenkins】自动化部署 maven 项目笔记

文章目录 前言1. Jenkins 新增 Maven 项目2. Jenkins 配置 Github 信息3. Jenkins 清理 Workspace4. Jenkins 配置 后置Shell脚本后记 前言 目标&#xff1a;自动化部署自己的github项目 过程&#xff1a;jenkins 配置、 shell 脚本积累 相关连接 Jenkins 官方 docker 指导d…

LangGraph中的State管理

本教程将介绍如何使用LangGraph库构建和测试状态图。我们将通过一系列示例代码&#xff0c;逐步解释程序的运行逻辑。 1. 基本状态图构建 首先&#xff0c;我们定义一个状态图的基本结构和节点。 定义状态类 from langgraph.graph import StateGraph, START, END from typi…

Excel的图表使用和导出准备

目的 导出Excel图表是很多软件要求的功能之一&#xff0c;那如何导出Excel图表呢&#xff1f;或者说如何使用Excel图表。 一种方法是软件生成图片&#xff0c;然后把图片写到Excel上&#xff0c;这种方式&#xff0c;因为格式种种原因&#xff0c;导出的图片不漂亮&#xff0c…

vue实现滚动条滑动到底部分页调取后端接口加载数据

一、案例效果 二、前提条件 接口返回数据 三、案例代码 子组件 const $emit defineEmits([cloneItem, updateList]);const props defineProps({rightList: {type: Array,},chartTableData: {type: Array as () > ChartListType[],},deleteChartInfo: {type: Object,}…

Ubuntu中使用多版本的GCC

我的系统中已经安装了GCC11.4&#xff0c;在安装cuda时出现以下错误提示&#xff1a; 意思是当前的GCC版本过高&#xff0c;要在保留GCC11.4的同时安装GCC9并可以切换&#xff0c;可以通过以下步骤实现&#xff1a; 步骤 1: 安装 GCC 9 sudo apt-get update sudo apt-get ins…

【Android】RecyclerView回收复用机制

概述 RecyclerView 是 Android 中用于高效显示大量数据的视图组件&#xff0c;它是 ListView 的升级版本&#xff0c;支持更灵活的布局和功能。 我们创建一个RecyclerView的Adapter&#xff1a; public class MyRecyclerView extends RecyclerView.Adapter<MyRecyclerVie…