【二叉搜索树】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

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Spring Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依

利用python实现对excel文件进行加密

《利用python实现对excel文件进行加密》由于文件内容的私密性,需要对Excel文件进行加密,保护文件以免给第三方看到,本文将以Python语言为例,和大家讲讲如何对Excel文件进行加密,感兴... 目录前言方法一:使用pywin32库(仅限Windows)方法二:使用msoffcrypto-too