c++中的二叉搜索树

目录

​编辑

一·概念:

二·性能分析:

三·实现步骤:

3·1插入:

3·2删除:

3·3查找:

四·应用(key与key_value):

4·1key模型:

4·2key_value模型:


一·概念:

静图展示:

动图展示:

①左子树不为空,则左子树节点值小于根节点值。

②右子树不为空,则右子树节点值大于根节点值。

③左右子树均为二叉搜索树。

④对于它可以插入相等的也可以插入不相等的,这里如果插入的话一般执行的就是覆盖操作,也就是不允许插入如:

注:以二叉树为底层的容器:map(key_value模型),set(key模型),multimap,multiset,其中前两个不支持插入相等的值,而后两者便支持。

二·性能分析:

最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为:O(log2 N)

最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为:O( N)

所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为:O(N)

下面是它的缺点:插入的数据在它中应该是有序的,而且要知道这样会可以随机访问里面的数据,那么插入与删除就变得复杂了,因此引出后面需要的平衡二叉树。

三·实现步骤:

下面把它的主体分为三点:插入,删除(复杂点),查找,(不支持修改,因为会改变这棵树的性质)。

3·1插入:

这里比较简单,就是找比这个节点值大就往右走,小就往左走,直到走到空,就可以开辟节点并插入,但是问题就是连接起来,因此需要保存上一个也就是parent节点:

bool insert(const k& key) {//头结点直接加:if (_root == nullptr) {_root = new bsnode<k>(key);return true;}else {//找到这个位置(通过搜索比较)bsnode<k>* parent = nullptr;//为了连接:bsnode<k>* 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 bsnode<k>(key);//完成连接操作:if (parent->_key > key) parent->_left = cur;else parent->_right = cur;return true;}}

3·2删除:

当然大体框架到了删除这一步一般就是难点了,因为考虑的情况很多,下面我们画图把它要考虑的情况画出:

代码:

bool erase(const k& key) {bsnode<k>* parent = nullptr;bsnode<k>* cur = _root;if (_root == nullptr) return false;//找到这个key的位置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 && cur->_right == nullptr) {//这里由于parent左右指针还和cur连一起,注意置空if (parent == nullptr) {delete cur;}else {if (parent->_left == cur) parent->_left = nullptr;else if (parent->_right == cur) parent->_right = nullptr;delete cur;}//这里最后发现是多余分出了,可以和后面归为一类,就是要么左空要么右空一类(下面就先不更改了)}//第二种情况:else if (cur->_left == nullptr) {if (parent == nullptr) _root = cur->_right;//后面防止parent为空故用elseelse {if (parent->_left == cur) parent->_left = cur->_right;//看到要访问parent的左指针,就//要考虑parent是否为空else if (parent->_right == cur) parent->_right = cur->_right;}delete cur;}//第三种情况:else if (cur->_right == nullptr) {if (parent == nullptr) _root = cur->_left;else {if (parent->_left == cur) parent->_left = cur->_left;else if (parent->_right == cur) parent->_right = cur->_left;}delete cur;}第四种情况:(相对复杂因为这里不能直接删,而是找一个正确位置的节点的key与它互换然后再删除那个节点)//这里通过证明可以发现替换的那个节点是cur的右节点的最左节点:因为既然是cur的右节点的最左节点一定比①cur的右key小,//然而既然能排到cur的右节点的那块最左边的这个节点,则它一定比cur大,②故就推出比cur的左节点key大else {bsnode<k>* er_right_min_p = cur;//它的父亲节点bsnode<k>* er_right_min = cur->_right;//那个被替换要删除的节点while (er_right_min->_left) {er_right_min_p = er_right_min;er_right_min = er_right_min->_left;//找到这个被替代节点}cur->_key = er_right_min->_key;//完成覆盖//下面判断这个节点是父亲左节点还是右节点连接的:if (er_right_min_p->_left == er_right_min)er_right_min_p->_left = er_right_min->_right;else if (er_right_min_p->_right == er_right_min)er_right_min_p->_right = er_right_min->_right;//这里有一种情况就是当while没有进去,也就是//er_right_min_p 与er_right_min未改变,这是就是父亲的右指针连接delete er_right_min;}return true;}}return false;
}

3·3查找:

这里由于不考虑相同值的情况,因此就按照它的性质去查找即可:

bool find(const k& key) {//这里也可搞成返回bsnode*类型方便使用bsnode<k>* cur = _root;while (cur) {if (cur->_key > key) cur = cur->_left;else if (cur->_key < key) cur = cur->_right;else return true;}return false;
}

四·应用(key与key_value):

4·1key模型:

上面提到了set容器就是key模型:

简单来说就是key不是决定了树的性质排列,然后值是对key来完成增删查等操作。

场景1:小区车牌号放行系统简单模拟。

场景2:检查单词是否出现拼写错误。

实现代码:

namespace K {template< typename k>struct bsnode {k _key;bsnode<k>* _left;bsnode<k>* _right;bsnode(const k& key) :_key(key),_left(nullptr),_right(nullptr) {}};template< typename k>class bstree {public:bstree() = default;bstree(bstree<k>& t) {_root = copy(t._root);//这里利用copy函数搞了个临时对象,为了不让swap操作这个root而是操作的临时对象}bstree<k> operator=(bstree<k> t) {swap(_root, t._root);//t自定义类型会调用拷贝构造然后swap交换的事这个临时对象,t不受影响return *this;}bool insert(const k& key) {//头结点直接加:if (_root == nullptr) {_root = new bsnode<k>(key);return true;}else {//找到这个位置(通过搜索比较)bsnode<k>* parent = nullptr;//为了连接:bsnode<k>* 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 bsnode<k>(key);//完成连接操作:if (parent->_key > key) parent->_left = cur;else parent->_right = cur;return true;}}bool find(const k& key) {//这里也可搞成返回bsnode*类型方便使用bsnode<k>* 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) {bsnode<k>* parent = nullptr;bsnode<k>* cur = _root;if (_root == nullptr) return false;//找到这个key的位置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 && cur->_right == nullptr) {//这里由于parent左右指针还和cur连一起,注意置空if (parent == nullptr) {delete cur;}else {if (parent->_left == cur) parent->_left = nullptr;else if (parent->_right == cur) parent->_right = nullptr;delete cur;}//这里最后发现是多余分出了,可以和后面归为一类,就是要么左空要么右空一类(下面就先不更改了)}//第二种情况:else if (cur->_left == nullptr) {if (parent == nullptr) _root = cur->_right;//后面防止parent为空故用elseelse {if (parent->_left == cur) parent->_left = cur->_right;//看到要访问parent的左指针,就//要考虑parent是否为空else if (parent->_right == cur) parent->_right = cur->_right;}delete cur;}//第三种情况:else if (cur->_right == nullptr) {if (parent == nullptr) _root = cur->_left;else {if (parent->_left == cur) parent->_left = cur->_left;else if (parent->_right == cur) parent->_right = cur->_left;}delete cur;}第四种情况:(相对复杂因为这里不能直接删,而是找一个正确位置的节点的key与它互换然后再删除那个节点)//这里通过证明可以发现替换的那个节点是cur的右节点的最左节点:因为既然是cur的右节点的最左节点一定比①cur的右key小,//然而既然能排到cur的右节点的那块最左边的这个节点,则它一定比cur大,②故就推出比cur的左节点key大else {bsnode<k>* er_right_min_p = cur;//它的父亲节点bsnode<k>* er_right_min = cur->_right;//那个被替换要删除的节点while (er_right_min->_left) {er_right_min_p = er_right_min;er_right_min = er_right_min->_left;//找到这个被替代节点}cur->_key = er_right_min->_key;//完成覆盖//下面判断这个节点是父亲左节点还是右节点连接的:if (er_right_min_p->_left == er_right_min)er_right_min_p->_left = er_right_min->_right;else if (er_right_min_p->_right == er_right_min)er_right_min_p->_right = er_right_min->_right;//这里有一种情况就是当while没有进去,也就是//er_right_min_p 与er_right_min未改变,这是就是父亲的右指针连接delete er_right_min;}return true;}}return false;}~bstree(){destroy(_root);}void inorder() {_inorder(_root);cout << endl;}private://中序遍历:void _inorder(const bsnode<k>* root) {if (root == nullptr) return;_inorder(root->_left);cout << root->_key << " ";_inorder(root->_right);}//后序删除:void destroy(bsnode<k>* root) {if (root == nullptr) return;destroy(root->_left);destroy(root->_right);delete root;root = nullptr;}//前序安装:bsnode<k>* copy(bsnode<k>* root) {//这里递归完成:假设copy的递归已经完成到最后一步,即只需要按照根节点把它的左右子树连起来即可if (root == nullptr) return nullptr;bsnode<k>* newnode = new bsnode<k>(root->_key);newnode->_left = copy(root->_left);newnode->_right = copy(root->_right);return newnode;}bsnode<k>* _root = nullptr;};
}

4·2key_value模型:

key包含了性质,而value不改变性质只是一个标志,可以更改,而查找和删除还是根据key来决定。

场景1:单词的英汉互译。

场景2:无人停车时长收费计算系统模拟。

场景3:一些物品的计数统计。

这里如果要是实现只需要再key模型代码完成一些对value的改变即可:

namespace K_Value {template< typename k,typename v>struct bsnode {k _key;v _value;bsnode<k,v>* _left;bsnode<k,v>* _right;bsnode(const k& key, const v& value) :_key(key),_value(value),_left(nullptr),_right(nullptr) {}};template< typename k, typename v>class bstree {public:bstree() = default;bstree(bstree<k,v>& t) {_root = copy(t._root);//这里利用copy函数搞了个临时对象,为了不让swap操作这个root而是操作的临时对象}bstree<k,v> operator=(bstree<k,v> t) {swap(_root, t._root);//t自定义类型会调用拷贝构造然后swap交换的事这个临时对象,t不受影响return *this;}bool insert(const k& key, const v& value) {//头结点直接加:if (_root == nullptr) {_root = new bsnode<k,v>(key,value);return true;}else {//找到这个位置(通过搜索比较)bsnode<k,v>* parent = nullptr;//为了连接:bsnode<k,v>* 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 bsnode<k,v>(key,value);//完成连接操作:if (parent->_key > key) parent->_left = cur;else parent->_right = cur;return true;}}bsnode<k,v>* find(const k& key) {bsnode<k,v>* cur = _root;while (cur) {if (cur->_key > key) cur = cur->_left;else if (cur->_key < key) cur = cur->_right;else return cur;}return nullptr;}bool erase(const k& key, const v& value) {bsnode<k,v>* parent = nullptr;bsnode<k,v>* cur = _root;if (_root == nullptr) return false;//找到这个key的位置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 && cur->_right == nullptr) {if (parent == nullptr) {delete cur;}else {if (parent->_left == cur) parent->_left = nullptr;else if (parent->_right == cur) parent->_right = nullptr;delete cur;}//这里最后发现是多余分出了,可以和后面归为一类,就是要么左空要么右空一类(下面就先不更改了)}//第二种情况:else if (cur->_left == nullptr) {if (parent == nullptr) _root = cur->_right;//后面防止parent为空故用elseelse {if (parent->_left == cur) parent->_left = cur->_right;//看到要访问parent的左指针,就//要考虑parent是否为空else if (parent->_right == cur) parent->_right = cur->_right;}delete cur;}//第三种情况:else if (cur->_right == nullptr) {if (parent == nullptr) _root = cur->_left;else {if (parent->_left == cur) parent->_left = cur->_left;else if (parent->_right == cur) parent->_right = cur->_left;}delete cur;}第四种情况:(相对复杂因为这里不能直接删,而是找一个正确位置的节点的key与它互换然后再删除那个节点)//这里通过证明可以发现替换的那个节点是cur的右节点的最左节点:因为既然是cur的右节点的最左节点一定比①cur的右key小,//然而既然能排到cur的右节点的那块最左边的这个节点,则它一定比cur大,②故就推出比cur的左节点key大else {bsnode<k,v>* er_right_min_p = cur;//它的父亲节点bsnode<k,v>* er_right_min = cur->_right;//那个被替换要删除的节点while (er_right_min->_left) {er_right_min_p = er_right_min;er_right_min = er_right_min->_left;//找到这个被替代节点}cur->_key = er_right_min->_key;//完成覆盖//下面判断这个节点是父亲左节点还是右节点连接的:if (er_right_min_p->_left == er_right_min)er_right_min_p->_left = er_right_min->_right;else if (er_right_min_p->_right == er_right_min)er_right_min_p->_right = er_right_min->_right;//这里有一种情况就是当while没有进去,也就是//er_right_min_p 与er_right_min未改变,这是就是父亲的右指针连接delete er_right_min;}return true;}}return false;}~bstree(){destroy(_root);}void inorder() {_inorder(_root);cout << endl;}private://中序遍历:void _inorder(const bsnode<k,v>* root) {if (root == nullptr) return;_inorder(root->_left);cout << root->_key << ":"<<root->_value;_inorder(root->_right);}//后序删除:void destroy(bsnode<k,v>* root) {if (root == nullptr) return;destroy(root->_left);destroy(root->_right);delete root;root = nullptr;}//前序安装:bsnode<k,v>* copy(bsnode<k,v>* root) {//这里递归完成:假设copy的递归已经完成到最后一步,即只需要按照根节点把它的左右子树连起来即可if (root == nullptr) return nullptr;bsnode<k>* newnode = new bsnode<k,v>(root->_key);newnode->_left = copy(root->_left);newnode->_right = copy(root->_right);return newnode;}bsnode<k,v>* _root = nullptr;};}

到此为止希望对你对二叉搜索树的理解有点帮助,感谢支持!!! 

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

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

相关文章

Linux(6)--CentOS目录

文章目录 1. 根目录2. cd目录切换命令3. CentOS目录介绍4. pwd命令介绍5. ls命令介绍5.1 ls5.2 ls -a5.3 ls -l 1. 根目录 Windows电脑的根目录是计算机(我的电脑)&#xff0c;然后C盘、D盘。 Linux系统的根目录是/&#xff0c;我们可以使用cd /进入根目录&#xff0c;然后使…

20240919 - 【PYTHON】辞职信

import tkinter as tk # 导入 tkinter 模块&#xff0c;并简写为 tk from tkinter import messagebox # 从 tkinter 导入 messagebox 子模块&#xff0c;用于显示消息框 from random import random # 从 random 模块导入 random 函数&#xff0c;用于生成随机数# 创建窗口对…

一本还没发布的书,能在Github上拿25.6k⭐️,熬夜也要读完的书

重磅&#xff01;从零构建大语言模型教程开源&#xff01; 自从ChatGPT发布以来&#xff0c;大型语言模型&#xff08;LLM&#xff09;大放异彩。 如今市面上关于大模型的书籍和教程可谓琳琅满目&#xff0c;但基本上都只是从原理和参数调优上讲解的&#xff0c;没有一本系统性…

借老系统重构我准备写个OpenAPI3.1版的API管理工具(附录屏演示)

前段时间一直在忙公司老系统重构的方案设计&#xff0c;其中最大的重构点就是前后端分离。为了加快前后端协同开发和对接的工作效率&#xff0c;我决定写一个公司内部使用的OpenAPI3.1版的API管理工具。 文章目录 有现成的工具为啥不用现有成熟方案初步成果展示录屏演示下一步计…

手语识别系统源码分享

手语识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

计算机专业的就业方向

计算机专业的就业方向 亲爱的新生们&#xff0c;欢迎你们踏上计算机科学的旅程&#xff01;作为一名计算机专业的学生&#xff0c;你们即将进入一个充满无限可能的领域。今天&#xff0c;我将为大家介绍计算机专业的一些主要就业方向&#xff0c;帮助你们了解未来的职业选择。…

Java面试篇基础部分-Java内部类介绍

首先需要了解什么是内部类,内部类就是定义在类的内部的类称为内部类,内部类可以根据不同的定义方式分为静态内部类、成员内部类、局部内部类和匿名内部类。 静态内部类 定义在类体内部的通过static关键字修饰的类,被称为静态内部类。静态内部类可以访问外部类的静态变量和…

深度学习对抗海洋赤潮危机!浙大GIS实验室提出ChloroFormer模型,可提前预警海洋藻类爆发

2014 年 8 月&#xff0c;美国俄亥俄州托莱多市超 50 万名居民突然收到市政府的一则紧急通知——不得擅自饮用自来水&#xff01; 水是人类生存的基本供给&#xff0c;此通告关系重大&#xff0c;发出后也引起了不小的恐慌。究其原因&#xff0c;其实是美国伊利湖爆发了大规模…

OpenCV运动分析和目标跟踪(4)创建汉宁窗函数createHanningWindow()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 此函数计算二维的汉宁窗系数。 createHanningWindow是OpenCV中的一个函数&#xff0c;用于创建汉宁窗&#xff08;Hann window&#xff09;。汉宁…

Give azure openai an encyclopedia of information

题意&#xff1a;给 Azure OpenAI 提供一部百科全书式的信息 问题背景&#xff1a; I am currently dabbling in the Azure OpenAI service. I want to take the default model and knowledge base and now add on to it my own unique information. So, for example, for mak…

Vert.x HttpClient调用后端服务时使用Idle Timeout和KeepAlive Timeout的行为分析

其实网上有大量讨论HTTP长连接的文章&#xff0c;而且Idle Timeout和KeepAlive Timeout都是HTTP协议上的事情&#xff0c;跟Vert.x本身没有太大关系&#xff0c;只不过最近在项目上遇到了一些问题&#xff0c;用到了Vert.x的HttpClient&#xff0c;就干脆总结一下&#xff0c;留…

react学习笔记一:react介绍

将view规划成一个个的组件&#xff0c;是一个响应式的声明式的设计。 虚拟dom&#xff0c;减少dom操作。vue的虚拟dom是在react的基础上拓展来的。 单向数据流&#xff1a;是一种数据流动的模式。数据流的方向是有上到下的&#xff0c;在react中主要是从父组件流向子组件。 …

C语言进阶四:(指针和数组笔试题解析1)

一维数组&#xff1a; sizeof是计算内存大小的&#xff0c;strlen是计算字符串的长度。 int main() {//一维数组int a[] {1,2,3,4};printf("%d\n", sizeof(a));printf("%d\n", sizeof(a 0));printf("%d\n", sizeof(*a));printf("%d\n&q…

GitLab邮箱发送邮件:如何实现自动化发信?

gitlab邮箱发送邮件设置教程&#xff1f;Gitlab邮箱配置和使用&#xff1f; GitLab不仅提供了代码版本控制、持续集成/持续部署等功能&#xff0c;还支持通过其内置的邮件功能实现自动化邮件发送。AokSend将深入探讨如何在GitLab中配置和使用邮箱发送邮件功能。 GitLab邮箱发…

ERP进销存管理系统的业务全流程 Axure高保真原型源文件分享

这是一套ERP进销存管理系统的业务全流程Axure高保真原型设计文档。 原型预览地址&#xff1a;https://ppndif.axshare.com 产品意义&#xff1a; 提高工作效率&#xff1a; 电子记账替代手工记账&#xff0c;减少工作负担和人为错误。 实时查看库存情况&#xff0c;减少盘点时…

Tomcat_WebApp

Tomcat的目录的介绍 /bin&#xff1a; 这个目录包含启动和关闭 Tomcat 的脚本。 startup.bat / startup.sh&#xff1a;用于启动 Tomcat&#xff08;.bat 文件是 Windows 系统用的&#xff0c;.sh 文件是 Linux/Unix 系统用的&#xff09;。shutdown.bat / shutdown.sh&#xf…

ICMC 2024 has Arrived, and We’ll See You There

It’s finally time for the International Cryptographic Module Conference this year! ICMC 2024 will perhaps be the most energized ICMC to date, as post-quantum cryptography (PQC) – a topic that’s been weighing on most of our minds – features prominently …

大模型研发全揭秘:带你掌握训练后模型的最佳存储方案

在大模型项目的研发中&#xff0c;模型保存是每个AI从业者都必须掌握的重要技能。保存模型不仅能让我们在未来进行推理和预测&#xff0c;还能帮助我们继续优化和调整模型。因此&#xff0c;掌握如何高效保存模型显得尤为重要。本文将通过详细的技术细节和清晰的步骤&#xff0…

使用密钥文件登陆Linux服务器

假设A服务器为登陆目标,已经运行ssh服务。 B服务器作为登陆发起端。 登陆A服务器,账户S。 运行命令: ssh-keygen -t rsa 此时账户S家目录下会自动创建目录“.ssh”,目录下会有id_rsa和id_rsa.pub两个文件。 id_rsa为私钥,id_rsa.pub为公钥。 id_rsa文件内容下载到B服务…

【无人机/平衡车/机器人】详解STM32+MPU6050姿态解算—卡尔曼滤波+四元数法+互补滤波(文末工程资料下载)

效果: 目录 基础知识详解 欧拉角 加速度计(Accelerometer)与姿态测量 陀螺仪(Gyroscope)与姿态测量 姿态解算算法1-互补滤波 姿态解算算法2-四元数法 姿态解算算法3-卡尔曼滤波 组成 1.预测状态方程 2. 预测协方差方程 3. 卡尔曼增益方程 4. 跟新最优值方程(卡尔…