C++:二叉搜索树模拟实现(KV模型)
C++:二叉搜索树模拟实现(KV模型)
- 前言
- 模拟实现KV模型
- 1. 节点封装
- 2、前置工作(默认构造、拷贝构造、赋值重载、析构函数等)
- 2. 数据插入(递归和非递归版本)
- 3、数据删除(递归和非递归版本)
- 3.1 查找待删除节点位置
- 3.2 删除数据及相关节点调整
- 3.3 完整代码以及递归和非递归版本
- 四、查找数据
- 五、中序遍历
- 六、所有代码
前言
二叉搜索树又称二叉排序树,他对数据有严格的要求,具体表现在以下几个方面:
- 如果一个根节点的左子树不为空,则左子树中所有节点的值都必须小于根节点的值;如果它的右子树不为空,则右子树中所有节点的值都必须大于根节点的值。
- 它的左右子树也都必须是一个二叉搜索树,也都必须满足第一条。
- 二叉搜索树中的每个节点都是唯一的,不允许重复!!!

二叉搜索树的实际应用主要分为K模型和KV模型。
- K模型:即Key作为关键码,二叉搜索树中只存储Key一个数据。而关键码则是待搜索的值。比如:我们经常通过软件查找是否存在某个单词,是否拼写正确。
- 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 删除数据及相关节点调整
插找到待删除数据后,显然如果只是简单将该节点删除,有可能将不满足二叉搜索树的要求,那怎么办呢?
删除数据分为以下三种情况:
- 左子树为空
左子树为空主要分为以下情形:右子树为空,左子树不为空;左右子树均为空(省略)。

不管上述那种情况,我们发现只需将父节点的下一个节点指向待删除节点的右指针即可。但需要注意的是,如果待删除节点为根节点,它将没有父节点,需要单独处理。
【代码实现】:
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;
}
- 右子树为空
右子树为空分为单纯右子树为空和左右子树均为空(省)。具体处理方式和左子树为空类似就不多说了。

【代码实现】:
//左右子树均不为空,查找右子树最小元素进行交换后删除
if (parent == _root)//cur为根节点
{_root = cur->_left;}else{if (parent->_key > cur->_key){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;
}
- 左右子树均不为空
这种情况我们可以查找左子树最大值或右子树最小值和待删除删除节点进行交换,交换后我们可以转化为上述两种子问题来删除数据。(接下来博主以交换右子树最小值为例)

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;};
}
相关文章:
C++:二叉搜索树模拟实现(KV模型)
C:二叉搜索树模拟实现(KV模型) 前言模拟实现KV模型1. 节点封装2、前置工作(默认构造、拷贝构造、赋值重载、析构函数等)2. 数据插入(递归和非递归版本)3、数据删除(递归和非递归版本…...
npm淘宝镜像源换新地址
新的淘宝npm镜像源地址:https://registry.npmmirror.com 切换新的镜像源 npm config set registry https://registry.npmmirror.com然后再执行以下操作查看是否成功 npm config list如果没安装过淘宝镜像源的,则直接安装 npm install -g cnpm --regi…...
十大排序算法之线性时间非比较类排序
线性时间非比较类排序 线性时间的算法执行效率也较高,从时间占用上看,线性时间非比较类排序要优于非线性时间排序,但其空间复杂度较非线性时间排序要大一些。因为线性时间非比较类排序算法会额外申请一定的空间进行分配排序,这也…...
容器基础:Docker 镜像如何保证部署的一致性?
Docker 镜像如何通过固化基础环境、固化依赖性和固化软件启动流程保证部署的一致性 Docker 镜像通过以下三个方面保证部署的一致性: 1. 固化基础环境: 镜像包含构建应用程序所需的所有环境依赖项,例如操作系统、库和工具。构建镜像时,所有…...
爪哇部落算法组2024新生赛热身赛题解
第一题(签到): 1、题意: 2、题解: 我们观察到happynewyear的长度是12个字符,我们直接从前往后遍历0到n - 12的位置(这里索引从0开始),使用C的substr()函数找到以i开头的长度为12的字…...
1123. 铲雪车(欧拉回路)
活动 - AcWing 随着白天越来越短夜晚越来越长,我们不得不考虑铲雪问题了。 整个城市所有的道路都是双向车道,道路的两个方向均需要铲雪。因为城市预算的削减,整个城市只有 1 辆铲雪车。 铲雪车只能把它开过的地方(车道)的雪铲干…...
网络协议与攻击模拟_15FTP协议
了解FTP协议 在Windows操作系统上使用serv-U软件搭建FTP服务 分析FTP流量 一、FTP协议 1、FTP概念 FTP(文件传输协议)由两部分组成:客户端/服务端(C/S架构) 应用场景:企业内部存放公司文件、开发网站时利…...
「效果图渲染」效果图与3D影视动画渲染平台
效果图渲染和3D影视动画渲染都是视觉图像渲染的领域应用。效果图渲染主要服务于建筑、室内设计和产品设计等行业,这些领域通常对视觉呈现的精度和细节有较高要求。与之相比,3D影视动画渲染则普遍应用于电影、电视、视频游戏和广告等媒体领域,…...
Blender_查看版本
Blender_查看版本 烦人的烦恼,没找见哪儿可以查看版本? 算是个隐蔽的角落!...
node.js 读目录.txt文件,用 xml2js 转换为json数据,生成jstree所需的文件
请参阅:java : pdfbox 读取 PDF文件内书签 请注意:书的目录.txt 编码:UTF-8,推荐用 Notepad 转换编码。 npm install elementtree ; npm install xml2js ; node.js 用 elementtree读目录.txt文件,用 xml2js 转换为…...
【Docker】02 镜像管理
文章目录 一、Images镜像二、管理操作2.1 搜索镜像2.1.1 命令行搜索2.1.2 页面搜索2.1.3 搜索条件 2.2 下载镜像2.3 查看本地镜像2.3.1 docker images2.3.2 --help2.3.3 repository name2.3.4 --filter2.3.5 -q2.3.6 --format 2.4 给镜像打标签2.5 推送镜像2.6 删除镜像2.7 导出…...
了解海外云手机的多种功能
随着社会的高度发展,海外云手机成为商家不可或缺的工具,为企业出海提供了便利的解决方案。然而,谈及海外云手机,很多人仍不了解其强大功能。究竟海外云手机有哪些功能,可以为我们做些什么呢? 由于国内电商竞…...
白酒:自动化生产线的优势与实践
随着科技的进步,自动化生产线在各行各业的应用越来越广泛。云仓酒庄的豪迈白酒在生产过程中,也积极引入自动化生产线,以提升生产效率、品质和安全性。 首先,自动化生产线能够显著提高生产效率。传统的手工生产线在生产过程中容易受…...
用HTML5实现灯笼效果
本文介绍了两种实现效果:一种使用画布(canvas)标签/元素,另一种不用画布(canvas)标签/元素主要使用CSS实现。 使用画布(canvas)标签/元素实现,下面,在画布上…...
Postgresql源码(120)事务XID分配与主备XID同步
参考 《Postgresql源码(25)子事务可见性判断和性能问题》 XID获取顶层入口 函数:AssignTransactionId static void AssignTransactionId(TransactionState s) {...优先给没有事务ID的父事务分配 确保父事务有 XID,以便子事务总是…...
B2077 角谷猜想(洛谷)
题目描述 所谓角谷猜想,是指对于任意一个正整数,如果是奇数,则乘 33 加 11,如果是偶数,则除以 22,得到的结果再按照上述规则重复处理,最终总能够得到 11。如,假定初始整数为 55&…...
排序算法---归并排序
原创不易,转载请注明出处。欢迎点赞收藏~ 归并排序是一种常见的排序算法,它采用了分治的思想。它将一个待排序的数组递归地分成两个子数组,分别对两个子数组进行排序,然后将排好序的子数组合并成一个有序数组。 具体的归并排序过…...
[WUSTCTF2020]朴实无华(特详解)
一开始说header出问题了 就先dirsaerch扫一遍 发现robot.txt 访问一下 去看看,好好好,肯定不是得 他一开始说header有问题,不妨抓包看看,果然有东西 访问看看,乱码修复一下,在之前的博客到过 <img src…...
下载已编译的 OpenCV 包在 Visual Studio 下实现快速配置
自己编译 OpenCV 挺麻烦的,配置需要耗费很长时间,编译也需要很长时间,而且无法保证能全部编译通过。利用 OpenCV 官网提供的已编译的 OpenCV 库可以节省很多时间。下面介绍安装配置方法。 1. OpenCV 官网 地址是:https://opencv…...
【Linux系统学习】3.Linux用户和权限
Linux用户和权限 1.认知root用户 1.1 root用户(超级管理员) 无论是Windows、MacOS、Linux均采用多用户的管理模式进行权限管理。 在Linux系统中,拥有最大权限的账户名为:root(超级管理员) 而在前期&#…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
