C++镌刻数据密码的树之铭文:二叉搜索树

文章目录

  • 1.二叉搜索树的概念
  • 2.二叉搜索树的实现
    • 2.1 二叉搜索树的结构
    • 2.2 二叉搜索树的节点寻找
      • 2.2.1 非递归
      • 2.2.2 递归
    • 2.3 二叉搜索树的插入
      • 2.3.1 非递归
      • 2.3.2 递归
    • 2.4 二叉搜索树的删除
      • 2.4.1 非递归
      • 2.4.2 递归
    • 2.5 二叉搜索树的拷贝
  • 3.二叉树的应用
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

继数据结构的二叉树学习,本篇进行更进一步的搜索二叉树,是一种更为常见的结构

1.二叉搜索树的概念

二叉搜索树简单来说就是一个排序树

它是具有以下性质的二叉树:

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

🔥值得注意的是: 每棵子树都满足该性质

2.二叉搜索树的实现

2.1 二叉搜索树的结构

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){ }
};
  • _left: 指向左子节点的指针。
  • _right: 指向右子节点的指针。
  • _key: 存储节点的键值

2.2 二叉搜索树的节点寻找

2.2.1 非递归

bool Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;
}

借助 cur 指针从根节点开始遍历二叉搜索树:

  • cur->_key 小于 key,则转向右子树继续查找
  • cur->_key 大于 key,则转向左子树继续查找
  • cur->_key 等于 key,说明找到了目标键值,返回 true
  • 若遍历结束 curnullptr,表示未找到目标键值,返回 false

2.2.2 递归

bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}
}

检查基本情况: 查看当前节点 root 是否为空。若为空,返回 false,递归结束
比较键值: 若当前节点不为空,将当前节点的键值 root->_key 与目标键值 key 进行比较重复,每次递归调用都会将问题规模缩小,直至满足基本情况或者找到目标节点

🔥值得注意的是: 注意这些非递归要放在 private,因为 root 也是 private,由于要控制子树,必须要传入 root,如果是 public 的话,就只能传入自己的 root,而不是二叉搜索树的 root,无法保证 root 的正确性

2.3 二叉搜索树的插入

2.3.1 非递归

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}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{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;
}

cur 为空时,说明已经找到了插入位置。创建一个新节点,并根据 parent 的键和要插入的键的大小关系,将新节点插入到 parent 的左子树或右子树中

🔥值得注意的是: 首先检查树是否为空,如果为空,则直接创建一个新节点作为根节点,并返回 true

2.3.2 递归

bool _InsertR(Node*& root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}
}

这里递归的流程和查找的递归代码几乎一样,唯一不同的是要传入的 root 需要加引用,这是因为这里的代码只执行了节点寻找创建的操作,那么当我们找到空节点并创建的时候,由于 root 是上一个 _InsertR 函数 root->_leftroot->_right 的别名,创建的时候相当于 root->_left = new Node(key)root->_right = new Node(key),这样才能完成链接

2.4 二叉搜索树的删除

2.4.1 非递归

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){// 左为空if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}// 右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}}// 左右都不为空 else{Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(leftMax->_key, cur->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;
}

首先先找到需要删除的节点,接着就需要分了讨论:

  1. 要删除的结点无孩子结点
  2. 要删除的结点只有左孩子结点
  3. 要删除的结点只有右孩子结点
  4. 要删除的结点有左、右孩子结点

🔥值得注意的是: 第一点可以直接看成只有一个节点的情况,即链接的是空节点

删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除

如果待删除节点 cur 的左子树为空,分两种情况处理:
如果 cur 就是根节点,那么将根节点更新为 cur 的右子树;如果 cur 不是根节点,则根据 cur 是其父节点 parent 的左子节点还是右子节点,相应地将 parent 的左指针或右指针指向 cur 的右子树

删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除

如果待删除节点 cur 的右子树为空,同样分两种情况:

cur 是根节点,将根节点更新为 cur 的左子树;若 cur 不是根节点,根据 curparent 的左子节点还是右子节点,将 parent 的左指针或右指针指向 cur 的左子树

在删除节点的左子树中寻找最大节点或者在它的右子树中寻找最小节点,用它的值填补到被删除节点中,再来处理该节点的删除问题–替换法删除

在这里插入图片描述

当待删除节点 cur 的左右子树都不为空时,为了保持二叉搜索树的性质,找到 cur 左子树中的最大节点 leftMax(即左子树中最右侧的节点)。通过一个 while 循环找到 leftMax,并记录其父亲节点 parent。然后交换 leftMaxcur 的键值,这样就将删除 cur 节点的问题转化为删除 leftMax 节点的问题,leftMax 由于是最大的节点,所以要么没有节点,要么只有左节点

🔥值得注意的是:

在这里插入图片描述

Node* parent = cur 而不是 Node* parent = nullptr,因为如果第一个左子节点就是 leftMax,那么 parent 就不会改变,使用 parent 的时候就会出问题

2.4.2 递归

bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;// 1、左为空// 2、右为空// 3、左右都不为空if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}
}

将即 rootleftMax 的键值进行交换,此时原本 leftMax 节点处的键值变为要删除的 key,由于交换后要删除的节点在左子树中,所以递归调用 _EraseR(root->_left, key) 继续在左子树中查找并删除这个键值为 key 的节点。因为在左子树中删除节点时,可能又会遇到不同的情况(如左子树为空、右子树为空或左右子树都不为空),所以递归调用可以继续处理这些情况,直到成功删除节点或者确定节点不存在

🔥值得注意的是:

在这里插入图片描述

这里 return _EraseR(root->_left, key) 不能写成 return _EraseR(leftMax, key)

因为 leftMax 只是个局部变量,对其进行操作没法改变 81 的链接

2.5 二叉搜索树的拷贝

Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_key);copyroot->_left = Copy(root->_left);copyroot->_right = Copy(root->_right);return copyroot;
}
  1. 为当前节点创建一个新的节点 copyroot,新节点的键值和原节点 root 的键值相同
  2. 递归调用 Copy 函数来拷贝原节点 root 的左子树,将拷贝结果赋值给新节点 copyroot 的左子节点指针 _left
  3. 同样地,递归调用 Copy 函数来拷贝原节点 root 的右子树,把拷贝结果赋值给新节点 copyroot 的右子节点指针 _right
  4. 最后返回新创建的节点 copyroot,该节点及其子树构成了原节点及其子树的深拷贝

3.二叉树的应用

🚩K模型: 即只有 key 作为关键码,结构中只需要存储 key 即可,关键码即为需要搜索到的值,主要判断在不在的场景

比如: 给一个单词 word,判断该单词是否拼写正确,具体方式如下:

  • 以词库中所有单词集合中的每个单词作为 key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

🚩KV模型: 每一个关键码 key,都有与之对应的值 value,即 <key, value> 的键值对,通过一个值找另外一个值

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文 <word, chinese> 就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是 <word, count> 就构成一种键值对
namespace key_value
{template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:BSTree():_root(nullptr){}void InOrder(){_InOrder(_root);cout << endl;}Node* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key, const V& value){return _InsertR(_root, key, value);}bool EraseR(const K& key){return _EraseR(_root, key);}private:bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;// 1、左为空// 2、右为空// 3、左右都不为空if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}}bool _InsertR(Node*& root, const K& key, const V& value){if (root == nullptr){root = new Node(key, value);return true;}if (root->_key < key){return _InsertR(root->_right, key, value);}else if (root->_key > key){return _InsertR(root->_left, key, value);}else{return false;}}Node* _FindR(Node* root, const K& key){if (root == nullptr)return nullptr;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return root;}}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root;};

key_value 模型主要是通过一个节点里包含两个值:keyvalue 实现的,只要找到了key 就能顺便找到 value,其余的函数逻辑等都与 K 模型几乎一致

🔥值得注意的是: 二叉搜索树的性能是不错的,插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能,

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们涉及到后续章节学习的 AVL树红黑树


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

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

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

相关文章

系统架构设计师:流水线技术相关知识点、记忆卡片、多同类型练习题、答案与解析

流水线记忆要点‌ ‌公式 总时间 (n k - 1)Δt 吞吐率 TP n / 总时间 → 1/Δt&#xff08;max&#xff09; 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 关键概念 周期&#xff1a;最长段Δt 冲突‌&#xff1a; ‌数据冲突&#xff08;RAW&#xff09; → 旁路/…

强制重装及验证onnxruntime-gpu是否正确工作

#工作记录 我们经常会遇到明明安装了onnxruntime-gpu或onnxruntime后&#xff0c;无法正常使用的情况。 一、强制重新安装 onnxruntime-gpu 及其依赖 # 强制重新安装 onnxruntime-gpu 及其依赖 pip install --force-reinstall --no-cache-dir onnxruntime-gpu1.18.0 --extra…

桌面我的电脑图标不见了怎么恢复 恢复方法指南

在Windows操作系统中&#xff0c;“我的电脑”或在较新版本中称为“此电脑”的图标&#xff0c;是访问硬盘驱动器、外部存储设备和系统文件的重要入口。然而&#xff0c;有些用户可能会发现桌面上缺少了这个图标&#xff0c;这可能是由于误操作、系统设置更改或是不小心删除造成…

2025.04.20【Lollipop】| Lollipop图绘制命令简介

Customize markers See the different options allowing to customize the marker on top of the stem. Customize stems See the different options allowing to customize the stems. 文章目录 Customize markersCustomize stems Lollipop图简介R语言中的Lollipop图使用ggp…

docker-compose搭建kafka

1、单节点docker-compose.yml version: 3 services:zookeeper:image: zookeeper:3.8container_name: zookeeperports:- "2181:2181"volumes:- ./data/zookeeper:/dataenvironment:ZOO_MY_ID: 1ZOO_MAX_CLIENT_CNXNS: 100kafka:image: bitnami/kafka:3.7container_na…

【问题】一招解决vscode输出和终端不一致的困扰

背景&#xff08;闲话Trae&#xff09; Trae是挺好&#xff0c;用了几天&#xff0c;发现它时不时检查文件&#xff0c;一检测就转悠半天&#xff0c;为此我把当前环境清空&#xff0c;就留一个正在调的程序&#xff0c;结果还照样检测&#xff0c;虽然没影响什么&#xff0c;…

Git,本地上传项目到github

一、Git的安装和下载 https://git-scm.com/ 进入官网&#xff0c;选择合适的版本下载 二、Github仓库创建 点击右上角New新建一个即可 三、本地项目上传 1、进入 要上传的项目目录&#xff0c;右键&#xff0c;选择Git Bash Here&#xff0c;进入终端Git 2、初始化临时仓库…

从零开始配置spark-local模式

1. 环境准备 操作系统&#xff1a;推荐使用 Linux 或 macOS&#xff0c;Windows 也可以&#xff0c;但可能会有一些额外的配置问题。 Java 环境&#xff1a;Spark 需要 Java 环境。确保安装了 JDK 1.8 或更高版本。 检查 Java 版本&#xff1a; bash 复制 java -version 如果…

前端~地图(openlayers)绘制车辆运动轨迹(仿高德)

绘制轨迹路线轨迹路线描边增加起点终点图标绘制仿高德方向箭头模仿车辆动态运动动画 车辆运行轨迹 车辆轨迹经纬度坐标 const linePoints [new Point([123.676031, 43.653421]),new Point([123.824347, 43.697124]),new Point([124.197882, 43.946811]),new Point([124.104498…

分布式之CAP原则:理解分布式系统的核心设计哲学

声明&#xff1a;CAP中的P原则都是需要带着的 在分布式系统的设计与实践中&#xff0c;CAP原则&#xff08;又称CAP定理&#xff09;是开发者必须掌握的核心理论之一。它揭示了分布式系统在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#x…

IF=40.8|肿瘤免疫:从免疫基因组学到单细胞分析和人工智能

一、写在前面 今天分享的是发表在《Signal Transduction and Targeted Therapy》上题目为"Technological advances in cancer immunity: from immunogenomics to single-cell analysis and artificial intelligence"的文章。 IF&#xff1a;40.8 DOI:10.1038/s41392…

深入理解 Spring @Bean 注解

在 Spring 框架中,@Bean 注解是用于显式地声明一个或多个 Bean 实例,并将其注册到 Spring 容器中的重要工具。与 @Component 系列注解不同的是,@Bean 是方法级别的注解,通常与 @Configuration 注解结合使用。本文将详细介绍 @Bean 注解的功能、用法及其应用场景。 1. @Bean…

Pycharm 如何删除某个 Python Interpreter

在PyCharm中&#xff0c;点击右下角的“Interpreter Settings”按钮&#xff0c;或者通过菜单栏选择“File” > “Settings”&#xff08;macOS用户选择“PyCharm” > “Preferences”&#xff09;。在设置窗口中&#xff0c;导航到“Project: [Your Project Name]” >…

如何改电脑网络ip地址完整教程

更改电脑的网络IP地址以满足特定的网络需求&#xff0c;本文将为您提供一份详细的步骤指南。其实&#xff0c;改变IP地址并不是一件复杂的事&#xff0c;能解决因为IP限制带来的麻烦。以下是操作指南&#xff1a; 方法一&#xff1a;Windows 系统&#xff0c;通过图形界面修改 …

Oracle--SQL性能优化与提升策略

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、导致性能问题的内在原因 系统性能问题的底层原因主要有三个方面&#xff1a; CPU占用率过高导致资源争用和等待内存使用率过高导致内存不足并需…

【go】什么是Go语言中的GC,作用是什么?调优,sync.Pool优化,逃逸分析演示

Go 语言中的 GC 简介与调优建议 Go语言GC工作原理 对于 Go 而言&#xff0c;Go 的 GC 目前使用的是无分代&#xff08;对象没有代际之分&#xff09;、不整理&#xff08;回收过程中不对对象进行移动与整理&#xff09;、并发&#xff08;与用户代码并发执行&#xff09;的三…

【unity实战】Animator启用root motion根运动动画,实现完美的动画动作匹配

文章目录 前言1、动画分类2、如何使用根位移动画&#xff1f; 一、根位移动画的具体使用1、导入人形模型2、导入动画3、配置动画参数4、配置角色Animator动画状态机5、使用代码控制人物前进后退 二、问题分析三、Humanoid动画中的Root Motion机制及相关配置1、Humanoid动画中的…

中间件--ClickHouse-10--海量数据存储如何抉择ClickHouse和ES?

在Mysql数据存储或性能瓶颈时&#xff0c;采用冷热数据分离的方式通常是一种选择。ClickHouse和Elasticsearch&#xff08;ES&#xff09;是两个常用的组件&#xff0c;但具体使用哪种组件取决于冷数据的存储目的、查询模式和业务需求等方面。 1、核心对比 &#xff08;1&…

服务器运维:服务器流量的二八法则是什么意思?

文章目录 用户行为角度时间分布角度应用场景角度 服务器流量的二八法则&#xff0c;又称 80/20 法则&#xff0c;源自意大利经济学家帕累托提出的帕累托法则&#xff0c;该法则指出在很多情况下&#xff0c;80% 的结果是由 20% 的因素所决定的。在服务器流量领域&#xff0c;二…

springboot对接豆包大模型

文档地址: 豆包大模型-火山引擎 模型广场地址: 账号登录-火山引擎 首先来到模型广场&#xff0c;选取你需要的模型,我这边要做图片理解的应用&#xff0c;所以选用了Doubao-1.5.vision-pro. 点立即体验&#xff0c;进入一个新的页面&#xff0c;可以上传图片&#xff0c;然后…