C++:二叉搜索树的底层模拟实现


 

概念:

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

搜索二叉树的操作:

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
  • 二叉搜索树需要满足左子树比根小,右子树比根大,每一棵树,每一颗子树都需要满足这个条件
  • 二叉搜索树使用中序遍历后,得出的遍历结果一定是一个升序序列
  • 二叉搜索树的的查询操作雷素与二分查找,但是却比二分查找要来的简单,因为搜索二叉树的删除和插入操作比二分查找更为的简单,且并不需要二分查找一样,当插入和删除后必须要在经历一次排序操作。 
  • 最后二叉搜索树是不允许进行节点内部的value 也就是节点数值的修改,这是因为二叉搜索树的排序操作和树的结构是因为节点的value进行链接和形成的

节点结构:

	template<class K>//struct BinarySearchTreeNodestruct BSTreeNode{BSTreeNode<K>* _left;//指向左子树指针BSTreeNode<K>* _right;//指向右子树指针K _key;//用来进行排序的value数值BSTreeNode(const K& key)//节点的初始化列表:_left(nullptr), _right(nullptr), _key(key){}};

插入操作:

 二叉搜索树的插入 插入的具体过程如下:

a. 树为空,则直接新增节点,赋值给root指针

b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

因为插入时需要保证插入之后还是二叉搜索树,所以插入操作需要进行一次的对比, 例如插入数字5,如果需要插入数字5,那么就需要进行依次的对比!

也就是和每一个节点进行对比,从根节点开始,比对比的节点小往左边移动,比对比的节点大往右移动,直到走到最后一个节点,走向空,需要记住每次插入的节点(非重复)最后都是会插入一个空的位置。

同时还需要注意,当插入的数据已经在树中存在了,而就需要注意搜索树中不会冗余,也就是插入一个相同的元素是不允许在同一个树中出现两次,这是不允许的!除非需要一些扩展。

		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;}

查找函数:

 二叉搜索树的查找:

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

b、最多查找高度次,走到到空,还没找到,这个值不存在。

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;}

删除函数:

 删除操作分为三种情况/两种操作:

a、删除的节点他的左子树是空的,则他的父节点要指向他的右子树,同理删除的节点他的右子树是空的,他的父节点要指向他的左子树

b、叶子节点,不过叶子节点可以和第一种情况相结合,让父节点指向删除的叶子节点的右子树

c、左右子树都不为空的:需要使用替换法,找该节点的右子树最小节点或者左子树最大节点

这里找右子树的最小节点。

操作一:情况a、情况b

删除的节点他的左子树是空的,则他的父节点要指向他的右子树,同理删除的节点他的右子树是空的,他的父节点要指向他的左子树。

同时这里还会诞生一个问题,被删除节点的左子树是空的,那父节点指向这个节点的右子树,但是是父节点的那一个指针指向呢?是右指针还是左指针?

 需要进行额外的判断,判断删除的节点是父节点的左子树还是右子树,左子树就左指针,右子树就右指针。

且还有一个细节:例如左边为空时,被删除节点的父亲点的要进行判断,判断删除的节点是父亲节点的左节点还是右节点,但是如果我们删除的是根节点,就如上图所示.

这种就需要提前进行判断,如果当前的节点是一个根节点,且左边为空时,那么根节点就需要进行替换,替换成这个根节点的右子树的头一个节点,如果是右边为空,那么这个根节点就需要替换成根节点的左子树的头一个节点。

				else{// 删除// 左为空,父亲指向我的右if (cur->_left == nullptr)//判断被删除的节点是否是他的左子树为空{//if(parent == nullptr)是否为根节点的判断if (cur == _root){//为根节点,那么根节点替换成根节点的右子树_root = cur->_right;}else{//不算根节点则进行父亲节点的指向判断if (cur == parent->_left){
//如果当前节点是父亲节点的左子树,那么就使用父亲节点的左指针指向这个节点的右子树parent->_left = cur->_right;}else{
//如果当前节点是父亲节点的右子树,那么就使用父亲节点的右指针指向这个节点的右子树parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){//if(parent == nullptr)//是否是根节点的判断if (cur == _root){_root = cur->_left;}else{// 右为空,父亲指向我的左if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}

操作二:

 左右子树都不为空的:需要使用替换法,找该节点的右子树最小节点或者左子树最大节点,这里找右子树的最小节点。

找到这个被删除节点的右子树,然后进入右子树后一直往左边走,找到被删除节点的右子树的 最小的数,而删除操作则需要吧要删除的节点和最小数替换(交换操作)

但是有产生一个问题,之后要删除的话,可能找不到要删除的节点,所以我们还需要一个最小节点的父亲节点,进行指向操作,所以在找右子树最小节点的时候,还得跟更新他的父节点。

同时删除后让父亲节点的左指针指向空(这里删除的节点是叶子节点没有字节的,所以删除节点的指向是null)

但是这种只能因对常规删除,如果删除的的节点中他的右子树是一个叶子节点呢,又或者,它是根节点呢:

就如上图这种情况,这种情况下删除根节点,那么右子树的最小节点就是根的右孩子节点

同时在我们的代码中,交换删除后,我们让父亲节点的左指针,指向被删除节点的右子树(常规操作是最小右节点的父亲节点指向空,这里的最小右节点刚好是根节点的有孩子),那么放在上图情况会直接崩掉,所以还需要进行一个判断

最后这个判断的前者是判断是否是正常情况,在正常情况中,最小右子树节点的父节点只是一个普通的节点,且最小右子树节点的父节点的左边一定是最小右子树节点,所以可以让父节点指向最小右字数节点的 右子树

而非常情况就是如上图这种情况,根节点的右孩子节点就是根节点的最小右子树节点,这种非常情况下,根节点的最小右子树节点在右边,所以需要让根节点的右指针指向最小右子树节点的 右子树。

else{// 左右都不为空,替换法删除// // 查找右子树的最左节点替代删除Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}swap(cur->_key, rightMin->_key);if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}
完成的删除函数代码: 
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{// 删除// 左为空,父亲指向我的右if (cur->_left == nullptr){//if(parent == nullptr)if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){//if(parent == 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* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}swap(cur->_key, rightMin->_key);if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}return true;}}return false;}

中序遍历:

void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}

需要注意,这里中序遍历这样写的原因是因为root根节点是私有的原因,所以在外是调不动中序遍历函数的,所以需要把中序遍历函数进行套用,放在私有成员内部,在外部套用一层调用即可使用。 


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

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

相关文章

2024第十六届“中国电机工程学会杯”数学建模A题B题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

SQL注入(sqli-labs第一关)

sqli-labs第一关 方法一&#xff1a;手工注入 来到第一关&#xff0c;图上说我们需要一个数字的参数 于是我们先手工注入?id1 and 11 跟?id1 and 12发现页面没有报错 每张截图上面页面中有select查询语句&#xff0c;这是我在第一关的源码中加上了echo "$sql ";…

若依修改表格滚动条宽度

找到目录ruoyi-ui/src/assets/styles/ruoyi.scss 添加滚动条样式 //修改滚动条的宽度和高度&#xff08;横向是高度&#xff0c;纵向是宽度&#xff09;::-webkit-scrollbar {width: 14px;height: 14px;background-color: transparent;}

A计算机上的程序与B计算机上部署的vmware上的虚拟机的程序通讯 如何配置?

环境&#xff1a; 在A计算机上运行着Debian11.3 Linux操作系统&#xff1b;在B计算机上运行着Windows10操作系统&#xff0c;并且安装了VMware软件&#xff0c;然后在VMware上创建了虚拟机C并安装了CentOS 6操作系统 需求&#xff1a; 现在A计算机上的程序需要同虚拟机C上的软…

思维导图在线怎么制作?推荐这些工具

思维导图在线怎么制作&#xff1f;在如今的快节奏时代中&#xff0c;思维导图作为一种高效的信息组织与思考工具&#xff0c;受到了广泛的应用。在线制作思维导图成打破了时间和空间的限制&#xff0c;使得团队协作变得更加便捷&#xff0c;个人创作也更为灵活。以下是四款备受…

如何将一个VPS上的网站全部迁移至另外一个VPS服务器

最近我们老的VPS即将到期&#xff0c;由于近期Hostease的VPS活动力度较大&#xff0c;我们购买了Hostease的VPS&#xff0c;购买后需要将原服务器的所有网站迁移到Hostease提供的VPS中。在Hostease技术人员的帮助下&#xff0c;我们成功进行了迁移&#xff0c;下面我就介绍此次…

信息收集方法合集 第1期

前言 在工作中&#xff0c;经常被问到某个文件怎么下载&#xff0c;原文来自哪里。索性把我知道的所有信息收集方法全部整理一遍&#xff0c;希望对大家有用&#xff0c;如果有帮助到你&#xff0c;非常荣幸&#xff0c;我会坚持分享我的学习、工作经验。 信息种类&#xff1…

JAVA毕业设计138—基于Java+Springboot+Vue的医院预约挂号小程序(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的医院预约挂号小程序(源代码数据库)138 一、系统介绍 本系统前后端分离带小程序和后台 小程序&#xff08;用户端&#xff09;&#xff0c;后台管理系统&a…

SR3-05S电机保护器 施耐德 EOCR-SR3

EOCR-SR3电机保护器 施耐德 EOCR主要产品有电子式电动机保护继电器&#xff0c;电子式过电流继电器&#xff0c;电子式欠电流继电器&#xff0c;电子式欠电压继电器&#xff0c;其它保护和监视装置&#xff0c;电流互感器。 系列型号&#xff1a; EOCR-SR3-05S EOCR-SR3-30…

C++数据结构之链表树图的存储

本文主要介绍用数组存储&#xff0c;结构只做简单介绍 目录 文章目录 前言 结构体实现 1、链表的存储 2、树的存储 3、图的存储 数组实现 1、链表实现 2、树和图的实现 总结 前言 在正常工程中&#xff0c;我们通常使用结构体或者类&#xff0c;来定义并使用如链表…

老板必读:防数据泄露,保卫您的商业秘密

在信息技术高速发展的今天&#xff0c;数据泄露已成为所有企业都必须正视的风险。对于企业而言&#xff0c;数据不仅仅是一堆数字和信息的集合&#xff0c;更是企业的核心竞争力与商业秘密的载体。一旦数据泄露&#xff0c;不仅会导致经济损失&#xff0c;还可能使企业信誉受损…

JS中的arguments是什么?

arguments是当我们不确定有多少个参数传递时&#xff0c;就可以使用argument来获取。在js中&#xff0c;arguments实际上就是当前函数的一个内置对象&#xff0c;存储了我们传递的所有实参。arguents的展示形式就是一个伪数组&#xff0c;所以我们可以对它进行遍历。 我们先来…