C++二叉搜索树

本章主要是二叉树的进阶部分,学习搜索二叉树可以更好理解后面的mapset的特性。

1.二叉搜索树概念

二叉搜索树的递归定义为:非空左子树所有元素都小于根节点的值,非空右子树所有元素都大于根节点的值,而左右子树也是二叉搜索树。

2.二叉搜索树实现

2.1.接口分析

2.1.1.查找

  1. 从根开始比较查找,比根大则往右边走查找,比根小则往左边走查找。
  2. 最多查找高度次,走到空,还没找到,则该值不存在。

2.1.2.插入

  1. 树为空,则直接新增节点,赋值给root指针
  2. 树不空,按二叉搜索树性质查找插入位置,插入新节点

2.1.3.删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回false。否则要删除的结点可能分下面四种情况:

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

看起来有待删除节点有四种情况,实际情况1可以与情况2或者3合并起来,因此真正的删除过程如下:

  1. 情况1:删除该结点且使被删除节点的双亲结点指向被删除结点的左/右孩子结点(直接删除))

  2. 情况2:在它的右子树中寻找中序下的第一个结点(关键码最小,也就是右子树中最小的结点),用它的值填补到被删除节点中,再来处理该结点的删除问题(替换法删除)

    补充:实际上情况2找左子树的最大节点也是可以的。

上述体现了一种“托孤”的现象,这和Linux中孤儿进程的托孤很是类似。

2.2.具体实现

#include <iostream>
#include <string>
using namespace std;template<typename K>//这里更加习惯写K,也就是关键值key的类型
struct BinarySearchTreeNode
{BinarySearchTreeNode<K>* _left;BinarySearchTreeNode<K>* _right;K _key; BinarySearchTreeNode(K key = K()) : _key(key), _left(nullptr), _right(nullptr) {}
};template<typename K>
class BinarySearchTree
{typedef BinarySearchTreeNode<K> Node;
public://BinarySearchTree() : _root(nullptr) {}BinarySearchTree() = default;//强制编译器生成默认的构造函数BinarySearchTree(const BinarySearchTree<K>& b){_root = copy(b._root);}BinarySearchTree<K>& operator=(BinarySearchTree<K> b)//b拷贝了一份{swap(_root, b._root);return *this;}~BinarySearchTree(){destroy(_root);}//1.插入bool insert(const K& key){/*对于第一个插入的节点就是根节点。至于数据冗余,我在这里定义不允许数据冗余,也就是不允许出现重复的数据节点。这样的搜索二叉树会受到数据先后顺序插入的影响(您也可定义允许)*///1.查看是否根节点if (_root == nullptr){_root = new Node(key);return true;}//2.寻找存放的位置Node* parent = nullptr;//存放root的父节点Node* root = _root;//遍历,从根节点开始while (root)//直到空为止{parent = root;if (root->_key < key) {root = root->_right;}else if(root->_key > key){root = root->_left;}else//root->_key == key{return false;//默认不允许重复数据}}//3.插入节点及数据root = new Node(key);if (parent->_key < key)//注意不可以直接赋值给root,不仅内存泄露还连接不上节点{parent->_right = root;}else{parent->_left = root;}return true;}bool insertR(const K& key){return _insertR(_root, key);}//2.删除bool erase(const K& key){/*寻找被删除的节点,删除后,如果是单子节点还好,如果是多子节点就需要找到一个托孤后依旧满足二叉搜索树性质的节点,因此删除有两种情况:A.被删除节点是叶子节点 或者 被删除节点的左或右孩子为空,直接将孩子节点替换被删除节点即可B.被删除节点拥有两个子节点,取右子树中最小的节点替代被删除的节点(当然也可以取左子树的最大节点)b1.最小节点没有右孩子,最小节点直接替代被删除节点,并且将最小节点的空孩子节点交给父节点领养b2.最小节点存在右孩子,最小节点直接替代被删除节点,并且将最小节点的右孩子节点交给父节点领养最后还需要注意删除根节点,根节点没有父节点的问题*/Node* parent = nullptr;Node* cur = _root;//1.寻找节点while (cur){if (cur->_key < key){parent = cur;//不可以和下一个if语句共用,会出现cur和parenat的情况,例如:test_1()中删除10的时候cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{//2.删除节点(找到了)if (cur->_left == nullptr)//2.1.左为空{if (parent == nullptr)//避免cur是根节点,没有父节点,例如:test_1()中删除11的时候{_root = cur->_right;delete cur;return true;}if (parent->_left == cur){parent->_left = cur->_right;}else//parent->_right == cur{parent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr)//2.2.右为空{if (parent == nullptr){_root = cur->_left;delete cur;return true;}if (parent->_left == cur){parent->_left = cur->_left;}else//parent->_right == cur{parent->_right = cur->_left;}delete cur;}else//2.3.左右均不为空,取左子树中最大的或者取右子树中最小的节点替代被删除的节点{Node* pminRight = cur;//注意不能为nullptr,因为有可能出现不进循环的情况Node* minRight = cur->_right;//我们选择找右数最小节点while (minRight->_left != nullptr)//找到最左节点,但是需要注意这个最左节点如果有右树,那就需要最左节点的父节点接管{pminRight = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;//替换相当于删除if (pminRight->_left == minRight)//最左节点的父节点托管最左节点的右树,注意可能有两种情况{pminRight->_left = minRight->_right;}else if (pminRight->_right == minRight)//最左节点的父节点托管最左节点的右树,注意可能有两种情况{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;}bool eraseR(const K& key){return _eraseR(_root, key);}//3.查找bool find(const K& key){Node* root = _root;while (root){if (root->_key < key){root = root->_right;}else if (root->_key > key){root = root->_left;}else{return true;}}return false;}bool findR(const K& key){return _findR(_root, key);}//4.打印void inOrder(){_inOrder(_root);cout << endl;}private://1.销毁(提供给析构)void destroy(Node*& root){if (root == nullptr)return;destroy(root->_left);destroy(root->_right);delete root;root = nullptr;}//2.拷贝(提供给拷贝构造)Node* copy(Node* root){if (root == nullptr){return nullptr;}Node* newroot = new Node(root->_key);newroot->_left = copy(root->_left);newroot->_right = copy(root->_right);return newroot;}//3.插入(提供给递归插入)bool _insertR(Node*& root, const K& key)//注意root是引用{if (root == nullptr){root = new Node(key);//这里由于传递的是引用,那么root就是上一级递归的root->_left或者root->_rightreturn true;}if (root->_key < key){return _insertR(root->_right, key);}else if (root->_key > key){return _insertR(root->_left, key);}else{return false;}}//4.删除(提供给递归插入)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//root->_key == key{Node* del = root;//保存要删除的节点if (root->_right == nullptr){root = root->_left;}else if (root->_left == nullptr){root = root->_right;}else//左右均不为空{Node* maxleft = root->_left;while (maxleft->_right != nullptr)//找左树的最大节点{maxleft = maxleft->_right;}swap(root->_key, maxleft->_key);return _eraseR(root->_left, key);//由于左树的最大节点必有一个空孩子节点,因此使用递归删除即可,可以看到递归的删除比非递归及其的简单明了(注意不可以直接传递maxleft,这是一个局部变量)}delete del;return true;}}//5.查找(提供给递归查找)bool _findR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key == key)return true;if (root->_key < key){return _isRecursionFind(root->_left, key);}else//root->_key > key{return _isRecursionFind(root->_right, key);}}//6.打印(提供给递归打印)void _inOrder(Node* root)//注意这里不能直接就拿_root当作缺省值了,因为缺省值只能是常量或者全局变量,而_root需要使用this->_root,而this指针是函数形参,不一定传过来了,别谈使用_root了{if (root == nullptr)return;_inOrder(root->_left);cout << root->_key << " ";_inOrder(root->_right);}//?.成员变量Node* _root;
};

这里我还为您提供了三个测试样例:

//普通测试
void test_1()
{BinarySearchTree<int> b;b.insert(6);b.insert(2);b.insert(1);b.insert(4);b.insert(-2);b.insert(10);b.insert(9);b.insert(11);b.inOrder();b.erase(6);b.inOrder();b.erase(2);b.inOrder();b.erase(10);b.inOrder();b.erase(1);b.inOrder();b.erase(4);b.inOrder();b.erase(9);b.inOrder();b.erase(11);b.inOrder();b.erase(-2);b.inOrder();
}
//头删测试(需要该_root为公有成员才可以测试)
void test_2()
{BinarySearchTree<int> b;b.insert(6);b.insert(2);b.insert(1);b.insert(4);b.insert(-2);b.insert(10);b.insert(9);b.insert(11);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();//b.erase(b._root->_key);//b.inOrder();
}
//递归测试
void test_3()
{BinarySearchTree<int> b;b.insertR(6);b.insertR(2);b.insertR(1);b.insertR(4);b.insertR(-2);b.insertR(10);b.insertR(9);b.insertR(11);BinarySearchTree<int> b1(b);b.inOrder();b.eraseR(6);b.inOrder();b.eraseR(2);b.inOrder();b.eraseR(10);b.inOrder();b.eraseR(1);b.inOrder();b.eraseR(4);b.inOrder();b.eraseR(9);b.inOrder();b.eraseR(11);b.inOrder();b.eraseR(-2);b.inOrder();b1.inOrder();b.inOrder();
}

3.二叉搜索树应用

3.1.Key模型

考虑“在不在”的问题,例如:

  1. 门禁系统
  2. 车库系统
  3. 单词检查、搜索…

查找对象是否在数据库中存在,这样的场景在现实中有很多。

3.2.Key/Value模型

通过一个值查找另外一个值,例如:

  1. 中英文互译
  2. 电话号码查询快递信息
  3. 验证码查询信息…

只需要在一个节点中包含一个数据对即可。

另外我们之前说过二叉搜索树一般不存储重复的元素,如果相同的元素可以让该元素绑定一个int元素形成键值对,这种情况的实际应用有:统计高频词汇。

补充:实际上,上面的这两种模型对标的是C++setmap容器,这些我们后续学习。

4.二叉搜索树分析

由于缺失平衡性,二叉搜索树在最不理想的状态(一颗斜树)查找的时间复杂度是 O ( n ) O(n) O(n),最好的效率是 O ( l o g 2 ( N ) ) O(log_{2}(N)) O(log2(N))

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

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

相关文章

GEE数据集——原住民土地(原住民土地地图)数据集

原住民土地&#xff08;原住民土地地图&#xff09; 土地承认是人们在日常生活中融入原住民存在和土地权利意识的一种方式。这通常在仪式、讲座或教育指南开始时进行。它可以是一种明确但有限的方式来认识殖民主义和第一民族的历史以及定居者殖民社会变革的需要。在这种情况下…

053基于web+springboot的宠物咖啡馆平台的设计与实现

欢迎大家关注&#xff0c;一起好好学习&#xff0c;天天向上 文章目录 一项目简介技术介绍 二、功能组成三、效果图四、 文章目录 一项目简介 本基于Spring Boot的宠物咖啡馆平台的设计与实现有管理员和用户以及看护师三个角色。用户功能有个人中心&#xff0c;咖啡菜品管理&a…

微信小程序授权登录获取用户的openid

小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识&#xff0c;快速建立小程序内的用户体系然而因为小程序中的openid不可以直接使用需要用code&#xff08;登录凭证&#xff09;去换取openid 获取openid的思路 获取openid首先需要调用小程序的login方法…

S5PV210(十):LCD

本文主要探讨210的LCD相关知识。 LCD LCD称液晶(透光背光呈色),可在电信号驱动下使液晶分子旋转,呈现不同的颜色(被动发光) lcd接口为TTL接口(5V为1&#xff0c;0V为0),不能传输太远,远距离传输方式:SoC(TTL) ->VGA-> LCD(TTL) 其他显设备:CRT(…

基于STM32设计的室内环境监测系统(华为云IOT)_2023

一、设计需求 基于STM32+华为云物联网平台设计一个室内环境监测系统,以STM32系列单片机为主控器件,采集室内温湿度、空气质量、光照强度等环境参数,将采集的数据结果在本地通过LCD屏幕显示,同时上传到华为云平台并将上传的数据在Android移动端能够实时显示、查看。 【1…

windows自动登陆

新建文本粘贴下面代码&#xff0c;另存为注册表文件 Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Driver Signing] "Policy"hex:00[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]"DefaultUserN…

2024最新mac电脑清理垃圾的软件有哪些?

mac电脑是许多人喜爱的电子产品&#xff0c;它拥有优美的设计、流畅的操作系统和强大的性能。但是&#xff0c;随着使用时间的增长&#xff0c;mac电脑也会积累一些不必要的垃圾文件&#xff0c;这些文件会占用宝贵的存储空间&#xff0c;影响电脑的运行速度和稳定性。因此&…

微信小程序获取openid

1.需要小程序中调用 wx.login获取临时code值&#xff08;每次获取的code值只能用一次&#xff09; wx.login({success (res) {console.log(res)} }) 打印结果为&#xff1a; 2.调用微信提供的apid接口&#xff0c;获取openid&#xff0c;入参需要三个参数&#xff1a;AppID(小…

C#中LINQtoObjects、LINQtoDataSet和LINQtoXML

目录 一、使用LINQ操作数组和集合 二、使用LINQ操作DataSet数据集 1.AsEnumerable()方法 2.CopyToDataTable()方法 3.AsDataView()方法 4.Take()方法 5.Sum()方法 6.示例 &#xff08;1&#xff09;源码 &#xff08;2&#xff09;生成效果 三、使用LINQ操作XML 1.…

Java字符串常用函数 详解5000字 (刷题向 / 应用向)

1.直接定义字符串 直接定义字符串是指使用双引号表示字符串中的内容&#xff0c;例如"Hello Java"、"Java 编程"等。具体方法是用字符串常量直接初始化一个 String 对象&#xff0c;示例如下&#xff1a; 1. String str"Hello Java"; 或者 …

【C++语法讲解】 | 运算符重构 | 三种运算符的重构方式 |代码演示

文章目录 1&#xff0c;简述2&#xff0c;结构体的定义1&#xff0c;结构体的声明2&#xff0c;结构体的申请 3.1 &#xff0c;在结构体中重构3.2 在结构体外进行重构 1&#xff0c;简述 通常情况下&#xff0c;我们会创建一些简单的数据结构以应对日常的算法使用&#xff0c;…

基于 NGram 分词,优化 Es 搜索逻辑,并深入理解了 matchPhraseQuery 与 termQuery

基于 NGram 分词&#xff0c;优化 Es 搜索逻辑&#xff0c;并深入理解了 matchPhraseQuery 与 termQuery 前言问题描述排查索引库分词&#xff08;发现问题&#xff09;如何去解决这个问题&#xff1f;IK 分词器NGram 分词器使用替换 NGram 分词器后进行测试matchPhraseQuery 查…

国际物流常见风险如何规避_箱讯科技

外贸物流是国际贸易的重要环节&#xff0c;其管理和效率的高低直接影响着贸易的成本和效益。因此&#xff0c;外贸企业应该重视物流的组织和管理&#xff0c;提高物流运作的效率。 国际物流基础知识 01什么是“双清包税”和“双清不包税” 双清包税上门又叫双清包税到门&…

CMake引用OSG

从CMake执行find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgUtil)这句;情况如下; 当前OSG已经安装好;环境变量添加了OSG_ROOT(其值是OSG安装的根目录),并且 %OSG_ROOT%\bin 添加到了path; 有一个警告,已经done了; Found osgDB: optimized;D:/OSGEarth/l…

wordpress版本识别

wordpress版本识别 1.通过RSS Feed识别 RSS Feed参考 访问网站/feed或者?feedrss 例如 默认结构&#xff1a;https://www.example.com/?feedrss2 其他结构&#xff1a;https://www.example.com/feed/ 返回结果中搜索 generator 可以看到直接是5.9.7版本 2.wpscan等工具扫一…

Powercli批量修改分布式交换机端口组

背景 需求&#xff1a; 批量修改虚拟机的分布式端口组 解决方式一&#xff1a; 三条命令解决&#xff1a;先获取目标虚拟机、获取目标端口组、修改虚拟机端口组、检查虚拟机状态。 $vm Get-VM -Name <虚拟机名称> $portGroup Get-VirtualPortGroup -Name <端口…

生成式人工智能:网络攻击者手中的破坏性力量

2022 年底&#xff0c;公开可用的生成式人工智能工具的推出使我们进入了人类历史上最大的技术革命之一。 一些人声称它的影响与互联网、手机、智能手机和社交媒体的引入一样大&#xff0c;甚至更大。这些新的生成式人工智能技术的采用和发展速度是我们以前从未见过的。 虽然这…

VBA根据Excel内容快速创建PPT

示例需求&#xff1a;根据Excel中选中的单元格内容&#xff08;3列&#xff09;如下图所示&#xff0c;在已打卡的PowerPoint文件中创建页面。 新增PPT Slide页面使用第二个模板页面&#xff0c;其中包含两个文本占位符&#xff0c;和一个图片占位符。将Excel选中区域中前两列写…

学习笔记二十七:K8S控制器Statefulset入门到企业实战应用

这里写目录标题 Statefulset控制器&#xff1a;概念、原理解读Statefulset资源清单文件编写技巧查看定义Statefulset资源需要的字段查看statefulset.spec字段如何定义&#xff1f;查看statefulset的spec.template字段如何定义 Statefulset使用案例&#xff1a;部署web站点State…

【Python基础】Python编程入门自学笔记,基础大全,一篇到底!

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…