C++:二叉搜索树模拟实现(KV模型)

C++:二叉搜索树模拟实现(KV模型)

  • 前言
  • 模拟实现KV模型
  • 1. 节点封装
  • 2、前置工作(默认构造、拷贝构造、赋值重载、析构函数等)
  • 2. 数据插入(递归和非递归版本)
  • 3、数据删除(递归和非递归版本)
    • 3.1 查找待删除节点位置
    • 3.2 删除数据及相关节点调整
    • 3.3 完整代码以及递归和非递归版本
  • 四、查找数据
  • 五、中序遍历
  • 六、所有代码

前言

 二叉搜索树又称二叉排序树,他对数据有严格的要求,具体表现在以下几个方面:

  1. 如果一个根节点的左子树不为空,则左子树中所有节点的值都必须小于根节点的值;如果它的右子树不为空,则右子树中所有节点的值都必须大于根节点的值。
  2. 它的左右子树也都必须是一个二叉搜索树,也都必须满足第一条。
  3. 二叉搜索树中的每个节点都是唯一的,不允许重复!!!
    在这里插入图片描述

 二叉搜索树的实际应用主要分为K模型和KV模型。

  1. K模型即Key作为关键码,二叉搜索树中只存储Key一个数据。而关键码则是待搜索的值。比如:我们经常通过软件查找是否存在某个单词,是否拼写正确。
  2. KV模型存储的数据中,每个Key对应一个Value,即键值对<Key, Value>。 我们经常通过Key去查找对应的Val.比如:我们通过英文来查找对应的中文,就是一个最常见的KV场景。

模拟实现KV模型

1. 节点封装

由于是KV模型,我们需要存储Key和Value俩个值。同时二叉搜索树也是二叉树,我们需要它的左右节点。因此节点疯转如下:

template<class K, class V>
struct BSTreeNode
{K _key;V _value;BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;//默认构造函数, 用于后续new创建节点BSTreeNode(const K& key, const V& value):_key(key), _value(value), _right(nullptr), _left(nullptr){}
};

2、前置工作(默认构造、拷贝构造、赋值重载、析构函数等)

接下来是KV模型封装的框架,以及默认构造、拷贝构造、赋值重载、析构函数。比较简单,就直接给出代码了哈。

template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;//节点重命名public://默认构造BSTree():_root(nullptr){}//拷贝构造BSTree(BSTree<K, V>& t){_root = Copy(t._root);}//赋值重载BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}//析构函数~BSTree(){Destory(_root);}private:Node* _root = nullptr;};
}

2. 数据插入(递归和非递归版本)

首先我们需要查找数据待插入的位置(为了保证插入数据后整体依然是一颗二叉搜索树).。同时查找插入位置时,只有key是有严格要求的,Value只是附带。
即:如果根节点为空,即是待插入数据位置;否则开始查找,如果待插入数据大于根节点往右子树节点走;如果待插入数据小于根节点往左子树节点走。不断循环,直到查找到空节点时,即为数据待插入的位置;如果查找到的大小和待插入数据值相等则返回false(确保二叉搜索树中的每个节点唯一)

【非递归版本】:

bool Insert(const K& key, const V& value)
{if (_root == nullptr)//根节点为空{_root = new Node(key, value);return true;}Node* cur = _root;Node* parent = nullptr;//后续插入数据链接时,需要和父节点相连while (cur){if (cur->_key > key)//待插入数据小于当前节点,往左子树查找{parent = cur;cur = cur->_left;}else if(cur->_key < key)//待插入数据大于当前节点,往右子树查找{parent = cur;cur = cur->_right;}else//待插入数据等于当前节点,不允许插入{return false;}}//链接Node* newNode = new Node(key, value); //链接时,我们无法确定插入节点时在父节点的左边还是右边,需要进一步比较if (parent->_key > key)parent->_left = newNode;elseparent->_right = newNode;return true;
}

【递归版本】:

bool InsertR(const K& key, const V& value)
{//由于我们查找位置需要从根节点开始查找,所以这里通过另一个函数来传递实现return _InsertR(_root, key, value);
}bool _InsertR(Node*& root, const K& key, const V& value)
{if (root == nullptr){//注意上述我们形参都是引用,所以不用新增Parent节点root = new Node(key, value);return true;}if (root->_key > key)//待插入数据小于当前节点,往左子树查找return _InsertR(root->_left, key, value);else if (root->_key < key)//待插入数据大于当前节点,往右子树查找return _InsertR(root->_right, key, value);elsereturn false;
}

3、数据删除(递归和非递归版本)

3.1 查找待删除节点位置

删除数据,我们首先需要和插入数据一样,先查找到待删除节点。和插入类似就不多说了。

【查找待删除数据】:

bool Erase(const K& key)
{if (_root == nullptr)//为空即不存在待删除数据return false;Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key)//待删除数据小于当前节点,往左子树查找{parent = cur;cur = cur->_left;}else if (cur->_key < key)//待删除数据大于当前节点,往右子树查找{parent = cur;cur = cur->_right;}else{//当前位置即为待删除节点,装备删除数据	}}return false;//整棵树中不存在待删除数据
}

3.2 删除数据及相关节点调整

插找到待删除数据后,显然如果只是简单将该节点删除,有可能将不满足二叉搜索树的要求,那怎么办呢?
删除数据分为以下三种情况:

  1. 左子树为空

左子树为空主要分为以下情形:右子树为空,左子树不为空;左右子树均为空(省略)。
在这里插入图片描述
 不管上述那种情况,我们发现只需将父节点的下一个节点指向待删除节点的右指针即可。但需要注意的是,如果待删除节点为根节点,它将没有父节点,需要单独处理。

【代码实现】:

if (cur->_left == nullptr)//左子树为空
{if (parent == _root)//cur为根节点{_root = cur->_right;}else{if (parent->_key > cur->_key)//待删除节点在父节点左子树中{parent->_left = cur->_right;}else//待删除节点在父节点右子树中{parent->_right = cur->_right;}}delete cur;
}
  1. 右子树为空

右子树为空分为单纯右子树为空和左右子树均为空(省)。具体处理方式和左子树为空类似就不多说了。
在这里插入图片描述
【代码实现】:

//左右子树均不为空,查找右子树最小元素进行交换后删除
if (parent == _root)//cur为根节点
{_root = cur->_left;}else{if (parent->_key > cur->_key){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;
}
  1. 左右子树均不为空

这种情况我们可以查找左子树最大值或右子树最小值和待删除删除节点进行交换,交换后我们可以转化为上述两种子问题来删除数据。(接下来博主以交换右子树最小值为例)
在这里插入图片描述

Node* subLeft = cur->_right;
Node* parent = cur;
while (subLeft->_left)
{parent = cur;subLeft = subLeft->_left;
}
//交换
swap(cur->_key, subLeft->_key);
swap(cur->_value, subLeft->_value);
//删除
if (parent->_right = subLeft)
{parent->_right = subLeft->_right;
}
else
{parent->_left = subLeft->_right;
}
delete subLeft;

3.3 完整代码以及递归和非递归版本

递归思路和非递归差球不多,就不一一分析了,下面直接给出两种实现方式代码。

【非递归版本】:

bool Erase(const K& key)
{if (_root == nullptr)return false;Node* cur = _root;Node* parent = nullptr;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 (parent == _root)//cur为根节点{_root = cur->_right;}else{if (parent->_key > cur->_key){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr)//右子树为空{if (parent == _root)//cur为根节点{_root = cur->_left;}else{if (parent->_key > cur->_key){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{//左右子树均不为空,查找右子树最小元素进行交换后删除Node* subLeft = cur->_right;Node* parent = cur;while (subLeft->_left){parent = cur;subLeft = subLeft->_left;}//交换swap(cur->_key, subLeft->_key);swap(cur->_value, subLeft->_value);//删除if (parent->_right = subLeft){parent->_right = subLeft->_right;}else{parent->_left = subLeft->_right;}delete subLeft;}return true;}}return false;
}

【递归版本】:

//删除:递归版本
bool EraseR(const K& key)
{return _EraseR(_root, key);//同理,由于需要根节点,在通过一层函数来实现
}
bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)//非找到return false;if (root->_key > key)//转化成递归子问题,在左子树中删除keyreturn _EraseR(root->_left, key);else if (root->_key < key)//转化成递归子问题,在右子树中删除keyreturn _EraseR(root->_right, key);else{//删除数据if (root->_left == nullptr){Node* del = root;root = root->_right;delete del;return true;}else if (_root->_right == nullptr){Node* del = root;root = root->_left;delete del;return true;}else{Node* subLeft = root->_right;while (subLeft->_left){subLeft = subLeft->_left;}//交换swap(root->_key, subLeft->_key);swap(root->_value, subLeft->_value);return _EraseR(root->_right, key); }}
}

四、查找数据

【递归版本】:

//查找:递归版本
Node* FindR(const K& key)
{return _FindR(_root, key);
}
Node* _FindR(Node*& root, const K& key)
{if (root == nullptr)return nullptr;if (root->_key > key)return _FindR(root->_left, key);else if (root->_key < key)return _FindR(root->_right, key);elsereturn root;
}

【非递归版本】:

//查找:非递归版本
Node* 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;}}return nullptr;
}

五、中序遍历

//中序遍历
void Inorder()
{_Inorder(_root);cout << endl;
}void _Inorder(Node* root)
{if (root == nullptr)return;_Inorder(root->_left);cout << root->_key << "->" << root->_value << " " << endl;_Inorder(root->_right);
}

六、所有代码

gitee:所有代码及测试代码

namespace KeyValue
{template<class K, class V>struct BSTreeNode{K _key;V _value;BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;//默认构造函数BSTreeNode(const K& key, const V& value):_key(key), _value(value), _right(nullptr), _left(nullptr){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:
////默认构造BSTree():_root(nullptr){}//拷贝构造BSTree(BSTree<K, V>& t){_root = Copy(t._root);}//赋值重载BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}//析构函数~BSTree(){Destory(_root);}
////插入, 非递归版本bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if(cur->_key < key){parent = cur;cur = cur->_right;}else{return false;}}//链接Node* newNode = new Node(key, value); if (parent->_key > key)parent->_left = newNode;elseparent->_right = newNode;return true;}// 插入: 递归遍布bool InsertR(const K& key, const V& value){return _InsertR(_root, key, value);}
///查找:非递归版本Node* 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;}}return nullptr;}//查找:递归版本Node* FindR(const K& key){return _FindR(_root, key);}
///删除:非递归版本bool Erase(const K& key){if (_root == nullptr)return false;Node* cur = _root;Node* parent = nullptr;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 (parent == _root)//cur为根节点{_root = cur->_right;}else{if (parent->_key > cur->_key){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr)//右子树为空{if (parent == _root)//cur为根节点{_root = cur->_left;}else{if (parent->_key > cur->_key){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{//左右子树均不为空,查找右子树最小元素进行交换后删除Node* subLeft = cur->_right;Node* parent = cur;while (subLeft->_left){parent = cur;subLeft = subLeft->_left;}//交换swap(cur->_key, subLeft->_key);swap(cur->_value, subLeft->_value);//删除if (parent->_right = subLeft){parent->_right = subLeft->_right;}else{parent->_left = subLeft->_right;}delete subLeft;}return true;}}return false;}//删除:递归版本bool EraseR(const K& key){return _EraseR(_root, key);}
///中序遍历void Inorder(){_Inorder(_root);cout << endl;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_key << "->" << root->_value << " " << endl;_Inorder(root->_right);}private:Node* Copy(Node*& root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_value);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}void Destory(Node*& root){if (root == nullptr)return;Destory(root->_right);Destory(root->_left);delete root;root = nullptr;}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key > key)return _EraseR(root->_left, key);else if (root->_key < key)return _EraseR(root->_right, key);else{//删除数据if (root->_left == nullptr){Node* del = root;root = root->_right;delete del;return true;}else if (_root->_right == nullptr){Node* del = root;root = root->_left;delete del;return true;}else{Node* subLeft = root->_right;while (subLeft->_left){subLeft = subLeft->_left;}//交换swap(root->_key, subLeft->_key);swap(root->_value, subLeft->_value);return _EraseR(root->_right, key); }}}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->_left, key, value);else if (root->_key < key)return _InsertR(root->_right, key, value);elsereturn false;}Node* _FindR(Node*& root, const K& key){if (root == nullptr)return nullptr;if (root->_key > key)return _FindR(root->_left, key);else if (root->_key < key)return _FindR(root->_right, key);elsereturn root;}Node* _root = nullptr;};
}

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

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

相关文章

开源免费的物联网网关 IoT Gateway

1. 概述 物联网网关&#xff0c;也被称为IOT网关&#xff0c;是一种至关重要的网络设备。在物联网系统中&#xff0c;它承担着连接和控制各种设备的重要任务&#xff0c;将这些设备有效地连接到云端、本地服务器或其他设备上。它既能够在广域范围内实现互联&#xff0c;也能在…

P1297 [国家集训队] 单选错位 对期望的理解

[国家集训队] 单选错位 - 洛谷 思路&#xff1a; 其实每个位置的得分只和前一个位置有关。 而他们俩的所有情况的期望就是答案的这部分。 ——这是难想的&#xff0c;我期望学的不好。 &#xff08;题目给的是每种情况的所有位置的和&#xff0c;全加起来是答案&#xff1…

【龙年大礼】| 2023中国开源年度报告!

【中国开源年度报告】由开源社从 2015 年发起&#xff0c;是国内首个结合多个开源社区、高校、媒体、风投、企业与个人&#xff0c;以纯志愿、非营利的理念和开源社区协作的模式&#xff0c;携手共创完成的开源研究报告。后来由于一些因素暂停&#xff0c;在 2018 年重启了这个…

基于 Python opencv 的人脸识别的酒店客房入侵系统的检测

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

排序算法---插入排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 插入排序是一种简单直观的排序算法&#xff0c;它的基本思想是将待排序的元素分为已排序和未排序两部分&#xff0c;每次从未排序部分中选择一个元素插入到已排序部分的合适位置&#xff0c;直到所有元素都插入到已排序部分…

FANUC机器人外部远程启动的相关参数设置示例

FANUC机器人外部远程启动的相关参数设置示例 如下图所示,在MENU---设置---选择程序中,设置程序选择模式:RSR(这个根据自己实际使用的自动启动方式来决定,你用RSR选RSR,用PNS就选PNS), 自动运行开始方法:选择UOP,即RSR1-RSR8的启动信号分别对应UI9-UI16, 最后,点击…

数字图像处理实验记录六(图像的傅里叶变换和频域处理)

前言&#xff1a; 一、基础知识 1&#xff0c;傅里叶变换是什么 傅里叶变换是一种线性积分变换&#xff0c;通俗来说&#xff0c;通过傅里叶变换就是把一段信号分解成若干个简谐波。 二、实验要求 1&#xff0e;产生一幅如图所示亮块图像f(x,y)&#xff08;256256 大小、…

真正免费的文件恢复软件easyrecovery2024中文版

easyrecovery数据恢复软件是一款广受好评的数据恢复工具&#xff0c;它能够有效地帮助用户恢复各种类型的文件。无论是照片、视频、音乐还是文档&#xff0c;都能轻松地找回这些重要文件。操作安全、用户可自主操作的数据恢复方案&#xff0c;它支持从各种各样的存储介质恢复删…

【芯片设计- RTL 数字逻辑设计入门 11 -- 移位运算与乘法】

请阅读【嵌入式开发学习必备专栏 】 文章目录 移位运算与乘法Verilog Codeverilog 拼接运算符&#xff08;{}&#xff09;Testbench CodeVCS 波形仿真 问题小结 移位运算与乘法 已知d为一个8位数&#xff0c;请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输…

飞书上传图片

飞书上传图片 1. 概述1.1 访问凭证2. 上传图片获取image_key1. 概述 飞书开发文档上传图片: https://open.feishu.cn/document/server-docs/im-v1/image/create 上传图片接口,支持上传 JPEG、PNG、WEBP、GIF、TIFF、BMP、ICO格式图片。 在请求头上需要获取token(访问凭证) …

MySQL优化器

优化器 MySQL存储引擎中存在了一个可插拔的优化器OPTIMIZER_TRACE&#xff0c;可以看到内部查询计划的TRACE信息&#xff0c;从而可以知道MySQL内部执行过程 查询优化器状态 show variables like optimizer_trace;Variable_name Valueoptimizer_trace enabledoff,one_lineoff…

Go语言每日一题——链表篇(七)

传送门 牛客面试笔试必刷101题 ----------------删除链表的倒数第n个节点 题目以及解析 题目 解题代码及解析 解析 这一道题与昨天的题目在解题思路上有一定的相似之处&#xff0c;都是基于双指针定义快慢指针&#xff0c;这里我们让快指针先走n步&#xff0c;又因为n一定…

吉他学习:右手拨弦方法,右手拨弦训练 左手按弦方法

第六课 右手拨弦方法https://m.lizhiweike.com/lecture2/29362775 第七课 右手拨弦训练https://m.lizhiweike.com/lecture2/29362708

vue.js基于springboot的实验室设备管理系统10345

(1)设备信息模块&#xff1a;记录设备的基本信息&#xff0c;如设备采购来源信息、设备需求量、当前数量、日期等。 (2) 用户模块&#xff1a;教师职工。实现对用户个人信息、消息管理和实验室设备的查询使用申请等。 (3) 管理员模块&#xff1a;实现对所有设备信息的增删改查&…

腾讯云游戏服务器购买入口,详细配置精准报价

2024年更新腾讯云游戏联机服务器配置价格表&#xff0c;可用于搭建幻兽帕鲁、雾锁王国等游戏服务器&#xff0c;游戏服务器配置可选4核16G12M、8核32G22M、4核32G10M、16核64G35M、4核16G14M等配置&#xff0c;可以选择轻量应用服务器和云服务器CVM内存型MA3或标准型SA2实例&am…

BUUCTF-Real-[Tomcat]CVE-2017-12615

目录 漏洞描述 一、漏洞编号&#xff1a;CVE-2017-12615 二、漏洞复现 get flag 漏洞描述 CVE-2017-12615&#xff1a;远程代码执行漏洞 影响范围&#xff1a;Apache Tomcat 7.0.0 - 7.0.79 (windows环境) 当 Tomcat 运行在 Windows 操作系统时&#xff0c;且启用了 HTTP P…

Qlik Sense : where exists

什么是Exists函数 Exists() 用于确定是否已经将特定字段值加载到数据加载脚本中的字段。此函数用于返回 TRUE 或 FALSE&#xff0c;这样它可以用于 LOAD 语句或 IF 语句中的 where 子句。 信息注释您也可使用 Not Exists() 来确定是否尚未加载字段值&#xff0c;但是如果要在…

Excel+VBA处理高斯光束

文章目录 1 图片导入与裁剪2 获取图片数据3 数据拟合 1 图片导入与裁剪 插入图片没什么好说的&#xff0c;新建Excel&#xff0c;【插入】->【图片】。 由于图像比较大&#xff0c;所以要对数据进行截取&#xff0c;选中图片之后&#xff0c;点击选项卡右端的【图片格式】…

【JavaWeb】头条新闻项目实现 基本增删改查 分页查询 登录注册校验 业务功能实现 第二期

文章目录 一、为什么使用token口令二、登录注册功能2.1 登录表单提交后端代码&#xff1a; 2.2 根据token获取完整用户信息代码实现&#xff1a; 2.3 注册时用户名占用校验代码实现&#xff1a; 2.4 注册表单提交代码实现&#xff1a; 三、头条首页功能3.1 查询所有头条分类3.2…

docker自定义镜像并使用

写在前面 本文看下如何自定义镜像。 ik包从这里 下载。 1&#xff1a;自定义带有ik的es镜像 先看下目录结构&#xff1a; /opt/program/mychinese [rootlocalhost mychinese]# ll total 16 -rw-r--r-- 1 root root 1153 Feb 5 04:18 docker-compose.yaml -rw-rw-r-- 1 el…