【二叉搜索树】K型与KV型二叉搜索树简单实现

2024-09-02 20:52
文章标签 简单 实现 搜索 二叉 kv

本文主要是介绍【二叉搜索树】K型与KV型二叉搜索树简单实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关于我:在这里插入图片描述


睡觉待开机:个人主页

个人专栏: 《优选算法》《C语言》《CPP》
生活的理想,就是为了理想的生活!
作者留言

PDF版免费提供倘若有需要,想拿我写的博客进行学习和交流,可以私信我将免费提供PDF版。
留下你的建议倘若你发现本文中的内容和配图有任何错误或改进建议,请直接评论或者私信。
倡导提问与交流关于本文任何不明之处,请及时评论和私信,看到即回复。


参考目录

  • 1.定位
  • 2.认识
  • 3.分类
  • 4.二叉搜索树的常见操作
    • 4.1插入
    • 4.2中序遍历
    • 4.3查找
    • 4.4删除(重点)
  • 5.K型二叉搜索树的代码
  • 6.KV型二叉搜索树的代码及其应用示例


我们先从宏观上看一看二叉搜索树究竟是个什么东西???他究竟是个什么东西。
可能很多人已经了解到他就是一个用来查找的数据结构而已,我想我下面从整体查找上能够让大家更好的对其进行定位,做到心中有数。

1.定位

我们知道,查找是日常生活中应用广泛的一种操作,对应到我们程序中来,就是在一堆数据中查找某个值呗。
那么说到查找,常见的查找有:二分查找、二叉搜索树查找、哈希查找、跳表查找、多叉树查找…(如下图)
在这里插入图片描述
那,二叉搜索树是个什么东西???

2.认识

首先,二叉搜索树又称二叉排序树

它满足下面特点,如果不满足那就不是二叉搜索树

  • 空树
  • 是二叉树且左子树所有结点的值比根小、右子树所有结点的值比根大

如果满足上述特点中的一条,那么就属于二叉搜索树
不过这里需要强调一点的是,一定要看清楚是不是左子树的所有结点的值都比根小,比如下图
在这里插入图片描述

在说完他的特点之后,我们来简单说一说他的优点和缺点。
至于优点嘛,比较适合用来查找东西,效率比较高,一般情况下能够达到O(logN)
缺点也是有的,特殊场景下,他的查找效率会退化为O(N),这样的话就直接跟遍历查找差不多了。为了说明这个情况,请看下面图例:
在这里插入图片描述

那么这个二叉搜索树能够用来干什么呢?
答案肯定可以用来查找啊,其次他的中序遍历结果就是排序升序,还可以用来查询某个值是否存在于二叉搜索树中、用一个词来查找另一个词(比如常见的中英翻译词典)、再比如说统计一篇文章中各个词出现的次数。

3.分类

从分类上来说,二叉搜索树可以分为两种:
第一种是K型二叉搜索树、另一种是KV型二叉搜索树

K型二叉搜索树:只有一个key值,这个key值要求不能重复存入二叉搜索树中,一般用来快速查找某个值是否存在于二叉搜索树中。
KV型二叉搜索树:有两个值,分别是key值和val值,key值不允许重复,val值不允许重复,一般用来key值来找另外一个val值,当然还可以用来统计一篇文章中出现了各个词出现的次数,在本篇文章中就做出示例代码。

4.二叉搜索树的常见操作

一个数据结构常见的操作就是增删查改,下面我们依次来进行介绍。

4.1插入

在这里插入图片描述
具体实现如下,我们可以这样写代码:

//插入
bool Insert(const K& key)
{//1.空树if (_root == nullptr){_root = new Node(key);}//2.一般情况//2.a 寻找该插入的位置Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_val < key)//比较大往右走{parent = cur;cur = cur->_right;}else if (cur->_val > key)//比较小往左走{parent = cur;cur = cur->_left;}else//相等,返回false{return false;//一般情况下,K型二叉搜索树不允许数据冗余}}

在这里我需要强调几点:
首先,在树中没有值的时候要进行特殊处理。
其次,要注意结点之间的链接问题,要做好链接工作。
之后 ,我们需要强调在一般的二叉搜索树中,key值不允许重复。

为了能够方便的打印这棵树,我们再写一个中序遍历。因为二叉搜索树的中序遍历恰好就是有序嘛,可以方便验证我们写的程序是否正确。

4.2中序遍历

private:void _InOrder(Node* pcur){if (pcur == nullptr){return;}_InOrder(pcur->left);cout << pcur->_val << " ";_InOrder(pcur->right);}public:void InOrder()//嵌套子函数,可以拿到_root使用{_InOrder(_root);}

这个地方我需要说明一点至于为什么要嵌套一层函数去调用中序遍历的原因。
这是因为要拿到私有_root成员,这样写比较方便。
当然也还有其他方法,
比如说你可以把调用中序遍历的函数设置为该类的友元函数,当然这种方法比较挫,因为本身调用函数和该类关系不大,算是一种弱关联。
还有可以学习Java一样,提供一个GetRoot函数,当然,这样也可以不失为一种好方法,只不过CPP不太习惯这种写法罢了。
再之后,把root作为缺省参数是不行的,因为访问root需要用到this作为跳板,但是this指针和缺省都属于函数参数,语法上是不支持的。
之后,就是用我这种函数嵌套的方法去处理,这种方法在CPP中是很常见的,CPP比较习惯用这种方法进行处理。

我们继续再写一个查找接口。

4.3查找

怎么实现查找接口呢?
在这里插入图片描述

bool Find(const K& key)
{Node* pcur = _root;while (pcur){if (pcur->_val < key){pcur = pcur->right;}else if (pcur->_val > key){pcur = pcur->left;}else{return true;//此时找到了,返回true}}return false;//此时没有找到返回false
}

接下来是重头戏,删除接口。
删除接口的情况比较多,因此算是本节中重点了。

4.4删除(重点)

在这里插入图片描述
对于第一种情况,
● 找到删除的结点和其父节点
● 弄明白删除结点还有左孩子还是右孩子
● 弄明白要删除结点对于其父节点来说是左孩子还是右孩子

对于第二种情况,
● 先找到要删除的结点及其父结点
● 再找到要替换的结点(一般而言左子树的最大值或者右子树的最小值就符合条件)及其父结点
● 两者交换
● 做好链接工作并删除

但是其中有个特殊情况,就是删除的结点恰好是根,所以需要对应进行特殊处理:
对于第一种情况,
ⅰ. 把根指向下一个结点
ⅱ. 修正parent,此时parent为空,不修正会对parent进行解引用操作
在这里插入图片描述

对于第二种情况,
ⅰ. 修正parent=cur
ⅱ. 要再次判断rightMin是其父亲的左孩子还是右孩子
在这里插入图片描述
所以实现是下面这样的:

//Erase
bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_val < key)//比较大往右走{parent = cur;cur = cur->_right;}else if (cur->_val > key)//比较小往左走{parent = cur;cur = cur->_left;}else//相等,找到了就开始准备删除{//1.只有一个孩子/没有孩子的情况//左为空,父亲指向我的右if (cur->_left == nullptr){if (cur == _root)//删除的是根的情况{_root = cur->_right;}else//删除的不是根{//具体父亲的左孩子指向我的右还是右孩子指向我的右呢?if (parent->_left == cur)//如果我是父亲的左孩子,那么父亲的左孩子就去继承我的右{parent->_left = cur->_right;}else//如果我是父亲的右孩子,那么父亲的右孩子就去继承我的右{parent->_right = cur->_right;}}delete cur;}else if(cur->_right == nullptr)//右为空,父亲指向我的左{//具体父亲的左孩子指向我的左还是右孩子指向我的左呢?if (cur == _root)//删除的是根的情况{_root = cur->_left;}else//删除的不是根{if (parent->_left == cur)//如果我是父亲的左孩子,那么父亲的左孩子就去继承我的左{parent->_left = cur->_left;}else//如果我是父亲的右孩子,那么父亲的右孩子就去继承我的左{parent->_right = cur->_left;}}delete cur;}else//2.我有两个孩子的情况{//此时替换法删除Node* rightMin = cur->_right;Node* rightMinParent = cur;//坑,如果下面while循环没进去,写空的话就会被解引用了while (rightMin->_left)//找合适的替换值{rightMinParent = rightMin;rightMin = rightMin->_left;}//交换swap(cur->_val, rightMin->_val);//删除前做好链接工作if (rightMinParent->_left == rightMin){rightMinParent->_left = rightMin->_right;}else{rightMinParent->_right = rightMin->_right;}//删除结点delete rightMin;}//end of two childrenreturn true;}//end of cur->_val == key}return false;
}

5.K型二叉搜索树的代码

//K型二叉搜索树
namespace Key
{//结点template<class K>struct BinarySearchNode{struct BinarySearchNode* _left;struct BinarySearchNode* _right;K _val;BinarySearchNode(const K& key):_left(nullptr),_right(nullptr),_val(key){}};//二叉搜索树template<class K>class BSTree{typedef struct BinarySearchNode<K> Node;private:Node* _root = nullptr;public://插入bool Insert(const K& key){//1.空树if (_root == nullptr){_root = new Node(key);}//2.一般情况//2.a 寻找该插入的位置Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_val < key)//比较大往右走{parent = cur;cur = cur->_right;}else if (cur->_val > key)//比较小往左走{parent = cur;cur = cur->_left;}else//相等,返回false{return false;//一般情况下,K型二叉搜索树不允许数据冗余}}//既不是空树,也不是相同的数字//2.b链接Node* newnode = new Node(key);if (parent->_val > key){parent->_left = newnode;}else{parent->_right = newnode;}cur = parent = newnode = nullptr;return true;}// end of Insert//中序遍历对外调用接口void InOrder(){_InOrder(_root);}//end of InOrder//查找bool Find(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_val < key)//比较大往右走{parent = cur;cur = cur->_right;}else if (cur->_val > key)//比较小往左走{parent = cur;cur = cur->_left;}else//相等,找到了返回true{return true;}}return false;}//Modify//注:二叉搜索树不允许去修改,后面KV型可以修改V值//Erasebool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_val < key)//比较大往右走{parent = cur;cur = cur->_right;}else if (cur->_val > key)//比较小往左走{parent = cur;cur = cur->_left;}else//相等,找到了就开始准备删除{//1.只有一个孩子/没有孩子的情况//左为空,父亲指向我的右if (cur->_left == nullptr){if (cur == _root)//删除的是根的情况{_root = cur->_right;}else//删除的不是根{//具体父亲的左孩子指向我的右还是右孩子指向我的右呢?if (parent->_left == cur)//如果我是父亲的左孩子,那么父亲的左孩子就去继承我的右{parent->_left = cur->_right;}else//如果我是父亲的右孩子,那么父亲的右孩子就去继承我的右{parent->_right = cur->_right;}}delete cur;}else if(cur->_right == nullptr)//右为空,父亲指向我的左{//具体父亲的左孩子指向我的左还是右孩子指向我的左呢?if (cur == _root)//删除的是根的情况{_root = cur->_left;}else//删除的不是根{if (parent->_left == cur)//如果我是父亲的左孩子,那么父亲的左孩子就去继承我的左{parent->_left = cur->_left;}else//如果我是父亲的右孩子,那么父亲的右孩子就去继承我的左{parent->_right = cur->_left;}}delete cur;}else//2.我有两个孩子的情况{//此时替换法删除Node* rightMin = cur->_right;Node* rightMinParent = cur;//坑,如果下面while循环没进去,写空的话就会被解引用了while (rightMin->_left)//找合适的替换值{rightMinParent = rightMin;rightMin = rightMin->_left;}//交换swap(cur->_val, rightMin->_val);//删除前做好链接工作if (rightMinParent->_left == rightMin){rightMinParent->_left = rightMin->_right;}else{rightMinParent->_right = rightMin->_right;}//删除结点delete rightMin;}//end of two childrenreturn true;}//end of cur->_val == key}return false;}private://中序遍历的主要逻辑,这里为了封闭性考虑,不把中序遍历的核心接口暴露出去//中序遍历void _InOrder(const Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << (*root)._val << " ";_InOrder(root->_right);}//end of _InOrder}; //end of class BSTree//BSTree专用的测试接口void BSTreeTest(){int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> tree;for (const auto& e : a){tree.Insert(e);}tree.InOrder();cout << endl << endl;cout << tree.Find(1) << endl;cout << tree.Find(100) << endl;for (const auto& e : a){tree.Erase(e);tree.InOrder();cout << endl;}}//end of BSTreeTest
}//end of namespace Key

6.KV型二叉搜索树的代码及其应用示例

namespace KV
{//结点template<class K, class V>struct BinarySearchTreeNode{struct BinarySearchTreeNode<K, V>* _left;struct BinarySearchTreeNode<K, V>* _right;K _key;V _val;BinarySearchTreeNode(const K& key, const V& val):_left(nullptr), _right(nullptr), _key(key), _val(val){}};//树template <class K, class V>class BinarySearchTree{typedef struct BinarySearchTreeNode<K, V> Node;private:Node* _root = nullptr;public:bool Insert(const K& key, const V& val){//空树情况if (_root == nullptr){Node* newnode = new Node(key, val);_root = newnode;newnode = nullptr;return true;}//一般情况//找出要插入位置的父结点Node* parent = nullptr, *cur = _root;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else//相等情况返回false{cout << key << ":该种情况被终止" << endl;return false;}}//链接Node* newnode = new Node(key, val);if (parent->_key > key){parent->_left = newnode;}else{parent->_right = newnode;}newnode = cur = parent = nullptr;return true;}//end of Insertvoid InOrder(){_InOrder(_root);}private:void _InOrder(const Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " " << root->_val << " ";_InOrder(root->_right);}//end of _InOrderpublic:bool 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 true;}}return false;}//end of Findbool Erase(const K& key){Node* cur = _root, * 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 == nullptr){parent = cur;_root = cur->_right;}if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}else if (cur->_right == nullptr)//要删除的右子树为空,那么父亲结点接过他的左子树就行{if (parent == nullptr){parent = cur;_root = cur->_left;}if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}else{//这种情况下需要替换法删除Node* leftMax = cur->_left;Node* leftMaxParent = cur;while (leftMax->_right){leftMaxParent = leftMax;leftMax = leftMax->_right;}//交换swap(leftMax->_key, cur->_key);swap(leftMax->_val, cur->_val);//链接并删除if (leftMaxParent->_left == leftMax){leftMaxParent->_left = leftMax->_left;}else{leftMaxParent->_right = leftMax->_left;}delete leftMax;}return true;}}//end of whilereturn false;}//end of Erase};//end of class BinarySearchTreevoid Test(){BinarySearchTree<int, string> tree;int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };string str[] = { "8", "3", "1", "10", "6", "4", "7", "14", "13" };for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++){tree.Insert(a[i], str[i]);tree.InOrder();cout << endl;}cout << tree.Find(1) << endl;cout << tree.Find(100) << endl;cout << tree.Find(109) << endl;cout << "-------------------------------" << endl;for (auto e : a){tree.Erase(e);cout << endl;tree.InOrder();}}
}//end of namespace KV

然后他的一个应用是用一个词搜索对应的另一个词,还有就是统计一篇文章中各个词出现了多少次。

void TestBSTree3()
{
// 输入单词,查找单词对应的中文翻译
BSTree<string, string> dict;
dict.Insert("string", "字符串");
dict.Insert("tree", "树");
dict.Insert("left", "左边、剩余");
dict.Insert("right", "右边");
dict.Insert("sort", "排序");
// 插入词库中所有单词
string str;
while (cin>>str)
{
BSTreeNode<string, string>* ret = dict.Find(str);
if (ret == nullptr)
{
cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
}
else
{
cout << str << "中文翻译:" << ret->_value << endl;
}
}
}
void TestBSTree4()
{
// 统计水果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
BSTree<string, int> countTree;
for (const auto& str : arr)
{
// 先查找水果在不在搜索树中
// 1、不在,说明水果第一次出现,则插入<水果, 1>
// 2、在,则查找到的节点中水果对应的次数++
//BSTreeNode<string, int>* ret = countTree.Find(str);
auto ret = countTree.Find(str);
if (ret == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}


好的,如果本篇文章对你有帮助,不妨点个赞~谢谢。
在这里插入图片描述


EOF

这篇关于【二叉搜索树】K型与KV型二叉搜索树简单实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核