c++:(map和set的底层简单版本,红黑树和AVL树的基础) 二叉搜索树(BST)底层和模拟实现

文章目录

  • 二叉搜索树的概念
  • 二叉搜索树的操作
    • 二叉搜索树的查找find
  • 二叉搜索树的模拟实现
    • 构造节点
    • insert
    • find
    • erase(细节巨多,面试可能会考)
      • a.叶子节点
      • b.有一个孩子
        • 左孩子
        • 右孩子
      • c.有两个孩子
        • 注意:
      • erase代码
    • 中序遍历
  • 二叉搜索树的应用
    • k模型
      • k模型模拟实现的总代码
    • k-value模型
      • k-value模型模拟实现的总代码
  • 二叉搜索树的不足
  • AVL树和红黑树的出现
  • 总结


二叉搜索树的概念

二叉搜索树,它的左子树的值比根的值小,右子树的值比根的值大
在这里插入图片描述
比如这一树,根节点的值8比左子树所有节点都大,比右子树的所有节点都小.

二叉搜索树的操作

二叉搜索树的查找find

因为二叉树有以上特性,所有使得它在搜索方面有极大的优势.
比如我们要找值为7的节点在不在
1.我们从根节点开始找,因为7<根节点的值8,所有根节点在左子树
在这里插入图片描述
2.现在根节点的值为3<7,所有在3的右子树中
在这里插入图片描述
3.现在根节点的值为6<7,所有在6的右子树中,刚好右子树的节点为7.
在这里插入图片描述
二叉搜索树最多寻找高度次,如果走到空还没有找到,说明这个值不存在

二叉搜索树的模拟实现

构造节点

	template<class K>struct BSTreeNode{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _val;BSTreeNode(const K& val):_left(nullptr), _right(nullptr), _val(val){}};

_val里面存节点的值

insert

bool insert(const K& val)
{//a.树为空,直接构造新节点赋值给根节点if (_root == nullptr){_root = new Node(val);return true;}Node* parent = nullptr;Node* cur = _root;//找到空的节点进行插入while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}// 二叉搜索树默认不允许重复else{return false;}}cur = new Node(val);if (parent->_val < val){parent->_right = cur;}else{parent->_left = cur;}return true;
}

插入有两种情况
a.树为空,直接构造新节点赋值给根节点
b.树不为空,按照二叉树的性质找到应该插入的空位置插入.

注意:
在b情况下,要找到新节点的位置,也要找到该节点的父亲节点,这样才能进行链接

假设要插入0节点,不光要找到0节点应该放的位置,还要找到0节点的父亲1,将他们链接起来
在这里插入图片描述

find

bool find(const K& val)
{Node* cur = _root;while (cur){if (cur->_val < val){cur = cur->_right;}else if (cur->_val > val){cur = cur->_left;}else{return true;}}return false;
}

按照二叉搜索树的概念,比根大的往右走,比根小的往左走.
找到返回true,找不到返回false

erase(细节巨多,面试可能会考)

erase里面的细节很多,要细品.

删除的节点有多种可能

a.叶子节点

在这里插入图片描述

比如这棵树我们要删除4节点,就只需要找到4节点和它的父亲节点6,让父亲节点6指向空,再删除4节点.

b.有一个孩子

特殊情况
要删除的是根节点,此时要更新新的根节点10.
在这里插入图片描述

if (_root == cur)
{_root = cur->_right;delete cur;
}
左孩子

右为空,父亲指向我的左
有一个左孩子,说明右子树为空.
此时要让父亲指向3的左边,此时不清楚是父亲的左边还是父亲的右边指向1节点

父亲的左指向我的左

父亲的右指向我的左
在这里插入图片描述
代码实现

if (cur->_right == nullptr)
{//删除头节点if (_root == cur){_root = cur->_left;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_left;elseparent->_left = cur->_left;delete cur;}
}
右孩子

左为空, 父亲指向我的右

//左为空, 父亲指向我的右
else if (cur->_left == nullptr)
{//删除头节点if (_root == cur){_root = cur->_right;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;delete cur;}
}

右孩子的判断和左孩子类似,方向反过来而已.

c.有两个孩子

在这里插入图片描述
找到左边的最大值或者右边的最小值,与目标值进行替换.
这里以右边的最小值为例.

我们寻找右边的最小值时,同时要找它的父亲节点,因为要对它的父亲节点进行修改.
找到右边的最小值为4,将4覆盖到cur上面,再删除right_min这个节点.
在这里插入图片描述

注意:

因为是寻找右子树的最小值,所以这个最小值理论上应该没有左子树.
如果有左子树,说明有更小的值.但是可能会有右子树.
所有要让right_min_parent左节点指向right_min的右节点.
这只是理论上,实际里面还有一个大坑
在这里插入图片描述
如果我们要删除的节点:cur和right_min_parent 指向同一个地方时,此时应该让right_min_parent 的右节点指向right_min的右节点.

//有两个孩子:找到左边的最大值或者右边的最小值,与目标值进行替换//让这个右最小节点的父亲的左边指向右最小的右边,因为它此时最多只有右孩子
else
{Node* right_min_parent = cur;Node* right_min = cur->_right;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}cur->_val = right_min->_val;//右最小节点,有坑,是连续存放的有序值if (cur->_right == right_min)right_min_parent->_right = right_min->_right;elseright_min_parent->_left = right_min->_right;delete right_min;
}

erase代码

bool erase(const K& val){Node* parent = _root;Node* cur = _root;//找到要删除的目标值while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}else{//只有一个孩子/叶子节点:让父亲节点指向子节点的右(nullptr)//右为空,父亲指向我的左if (cur->_right == nullptr){//删除头节点if (_root == cur){_root = cur->_left;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_left;elseparent->_left = cur->_left;delete cur;}}//左为空, 父亲指向我的右else if (cur->_left == nullptr){//删除头节点if (_root == cur){_root = cur->_right;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;delete cur;}}//有两个孩子:找到左边的最大值或者右边的最小值,与目标值进行替换//让这个右最小节点的父亲的左边指向右最小的右边,因为它此时最多只有右孩子else{Node* right_min_parent = cur;Node* right_min = cur->_right;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}cur->_val = right_min->_val;//右最小节点,有坑,是连续存放的有序值if (cur->_right == right_min)right_min_parent->_right = right_min->_right;elseright_min_parent->_left = right_min->_right;delete right_min;}return true;}}return false;}

中序遍历

		void MidOrder(){_MidOrder(_root);cout << endl;}private:void _MidOrder(const Node* root){if (root == nullptr){return;}_MidOrder(root->_left);std::cout << root->_val << " ";_MidOrder(root->_right);}

首先,中序的搜索方式是左子树 根 右子树.按照这个顺序就能有序的取出搜索二叉树里面的值了

为什么会有两个函数?
因为函数的形参没有this指针,没法调用_root根节点,我们需要另外一个函数来传_root根节点

二叉搜索树的应用

k模型

k模型跟我们上面实现的一样,只存储一个值
比如:我们可以用这个功能查找到我们英文作文里面的拼写错误的单词.
我们可以把词库里面所有的英语单词丢进这个二叉搜索树,再遍历整个作文,检查每个单词是否存在,不存在就报错.

k模型模拟实现的总代码

namespace shh1
{template<class K>struct BSTreeNode{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _val;BSTreeNode(const K& val):_left(nullptr), _right(nullptr), _val(val){}};//k模型template<class K>class BSTree{typedef BSTreeNode<K> Node;public:bool insert(const K& val){if (_root == nullptr){_root = new Node(val);return true;}Node* parent = nullptr;Node* cur = _root;//找到空的节点进行插入while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}// 二叉搜索树默认不允许重复else{return false;}}cur = new Node(val);if (parent->_val < val){parent->_right = cur;}else{parent->_left = cur;}return true;}bool erase(const K& val){Node* parent = _root;Node* cur = _root;//找到要删除的目标值while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}else{//只有一个孩子/叶子节点:让父亲节点指向子节点的右(nullptr)//右为空,父亲指向我的左if (cur->_right == nullptr){//删除头节点if (_root == cur){_root = cur->_left;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_left;elseparent->_left = cur->_left;delete cur;}}//左为空, 父亲指向我的右else if (cur->_left == nullptr){//删除头节点if (_root == cur){_root = cur->_right;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;delete cur;}}//有两个孩子:找到左边的最大值或者右边的最小值,与目标值进行替换//让这个右最小节点的父亲的左边指向右最小的右边,因为它此时最多只有右孩子else{Node* right_min_parent = cur;Node* right_min = cur->_right;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}cur->_val = right_min->_val;//右最小节点,有坑,是连续存放的有序值if (cur->_right == right_min)right_min_parent->_right = right_min->_right;elseright_min_parent->_left = right_min->_right;delete right_min;}return true;}}return false;}bool find(const K& val){Node* cur = _root;while (cur){if (cur->_val < val){cur = cur->_right;}else if (cur->_val > val){cur = cur->_left;}else{return true;}}return false;}void MidOrder(){_MidOrder(_root);cout << endl;}private:void _MidOrder(const Node* root){if (root == nullptr){return;}_MidOrder(root->_left);std::cout << root->_val << " ";_MidOrder(root->_right);}private:Node* _root = nullptr;};void BST_Test1(){int a[] = { 6,5,1,4,7,2,3,8,9,11,55,68,-1 };BSTree<int> t;for (auto e : a){t.insert(e);}t.MidOrder();}void BST_Test2(){int a[] = { 8 };BSTree<int> t;for (auto e : a){t.insert(e);}t.MidOrder();for (auto e : a){t.erase(e);t.MidOrder();}}
}

k-value模型

每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
这个在我们日常生活很常见,比如词典的翻译,我们在key里面存英语单词,value里面存相对应的中文翻译.
我们就可以通过输入英文单词得到其对应的中文翻译.

下面稍作演示:

void TestBSTree(){BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "删除");dict.Insert("left", "左边");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_val << endl;}else{cout << "单词拼写错误" << endl;}}}

在这里插入图片描述

k-value模型模拟实现的总代码

k-value模型的代码和上面的key模型类似,我们只需要要添加新节点的时候再加一个值就行.

namespace shh2
{template<class K, class V>struct BSTreeNode{typedef BSTreeNode<K, V> Node;Node* _left;Node* _right;K _key;V _val;BSTreeNode(const K& key, const V& val):_left(nullptr), _right(nullptr), _key(key), _val(val){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;Node* _root = nullptr;public:bool Insert(const K& key, const V& val){//头节点if (_root == nullptr){_root = new Node(key, val);return true;}Node* parent = _root;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, val);if (parent->_key < key)parent->_right = cur;elseparent->_left = cur;return true;}Node* 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 cur;}}return nullptr;}bool Erase(const K& key){Node* parent = _root;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 (cur == _root){_root = cur->_right;delete cur;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;}}//右为空,父亲的左/右指向我的左else if (cur->_right == nullptr){// 如果为根节点if (cur == _root){_root = cur->_left;delete cur;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}}//两个孩子 找到cur左子树的最大值替换else{Node* left_max_parent = cur;Node* left_max = cur->_left;while (left_max->_right){left_max_parent = left_max;left_max = left_max->_right;}swap(cur->_key, left_max->_key);if (left_max_parent->_left = left_max)left_max_parent->_left = left_max->_left;elseleft_max_parent->_right = left_max->_left;delete left_max;}return true;}}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_val << endl;_InOrder(root->_right);}void InOrder(){_InOrder(_root);}};void TestBSTree(){BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "删除");dict.Insert("left", "左边");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_val << endl;}else{cout << "单词拼写错误" << endl;}}}
};

二叉搜索树的不足

当二叉搜索树有序存入了一段值
在这里插入图片描述
这棵树会退化成单叉树,因为插入,查找和删除的时间复杂度都是高度次,
所以在这种情况下插入,查找和删除的时间复杂度会接近于N.搜索二叉树也就失去了它的优势.

AVL树和红黑树的出现

怎么解决这个问题呢,就要用到AVL和红黑树了.
在插入的时候,AVL树会查看树的高度是否平衡,
左子树和右子树的高度差不超过1.超过1会让树的几个节点之间发生旋转,最终这棵树会变成这样.

在这里插入图片描述
我们平时调用的容器map和set底层就是用AVL树和红黑树生成的.

总结

二叉搜索树的插入和查找不难,但是它的删除细节很多,分类很细,一不留神容易掉坑里面,面试也经常会考.大家如果不懂的话,要多看几遍.

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

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

相关文章

代码随想录算法训练营第六十天| LeetCode647. 回文子串 、516.最长回文子序列

一、LeetCode647. 回文子串 题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/0647.%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.html 状态&#xff1a;已解决 1.思路 这道题我只想出来了暴力解法&#xff0c;动规解法并没有想出来。根据视频讲解才把它想出来。…

聚观早报 | 苹果新款iPad Pro发布;国产特斯拉4月交付量

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 5月9日消息 苹果新款iPad Pro发布 国产特斯拉4月交付量 iOS 18新功能爆料 真我GT Neo6续航细节 三星Galaxy Z F…

智慧公厕,小民生里的“大智慧”!

公共厕所是城市社会生活的基础设施&#xff0c;而智慧公厕则以其独特的管理模式为城市居民提供更优质的服务。通过智能化的监测和控制系统&#xff0c;智慧公厕实现了厕位智能引导、环境监测、资源消耗监测、安全防范管理、卫生消杀设备、多媒体信息交互、自动化控制、自动化清…

uniapp——弹出键盘遮挡住输入框 textarea,处理方法

案例 在写输入框的时候会遇见 键盘遮挡住部分textarea框的一部分&#xff0c;使用cursor-spacing处理即可 修改后&#xff1a; 其他问题&#xff1a; 调起键盘输入时&#xff0c;不希望上方的内容被顶上去 代码 <view class"commentBox" :style"botto…

什么是HTTP?

什么是HTTP&#xff1f; HTTP基本概念HTTP 是什么&#xff1f;HTTP 常见的状态码有哪些&#xff1f;HTTP 常见字段有哪些&#xff1f; HTTP特性HTTP/1.1 的优点有哪些&#xff1f;HTTP/1.1 的缺点有哪些&#xff1f; HTTP基本概念 HTTP 是什么&#xff1f; HTTP 是超文本传输…

基于Springboot的果蔬作物疾病防治系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的果蔬作物疾病防治系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

4.【Orangepi Zero2】Linux定时器(signal、setitimer),软件PWM驱动舵机(SG90)

Linux定时器&#xff08;signal、setitimer&#xff09;&#xff0c;软件PWM驱动舵机&#xff08;SG90&#xff09; signalsetitimer示例 软件PWM驱动舵机&#xff08;SG90&#xff09; signal 详情请看Linux 3.进程间通信&#xff08;shmget shmat shmdt shmctl 共享内存、si…

内容安全(DPI和DFI解析)

内容安全前言&#xff1a; 防火墙的本质其实就是包过滤&#xff0c;我们通常所说的安全设备&#xff08;如&#xff1a;IPS、IDS、AV、WAF&#xff09;的检测重心是应用层。下一代防火墙基于传统防火墙的拓展能力&#xff0c;就是可以将以上的安全设备模块集成在一起&#xff0…

HackMyVM-Animetronic

目录 信息收集 arp nmap nikto whatweb WEB web信息收集 feroxbuster steghide exiftool hydra ssh连接 提权 系统信息收集 socat提权 信息收集 arp ┌──(root㉿0x00)-[~/HackMyVM] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 08:00:27:9d:6d:7…

anaconda虚拟环境pytorch安装

1.先创建conda的虚拟环境 conda create -n gputorch python3.102.激活刚刚创建好的虚拟环境 conda activate gputorch3.设置国内镜像源 修改anaconda的源&#xff0c;即修改.condarc配置文件 .condarc在 home/用户/user/ conda config --add channels https://mirrors.tuna.…

题目----力扣--移除链表元素

题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5]示例 2&#xff1a; 输入&…

【Python爬虫实战入门】:教你一个程序实现PPT模版自由

文章目录 &#x1f4a5;一、PPT模版爬取&#x1f525;1.1 第一个爬虫&#x1f6b2;1. 获取下载页面链接 ❤️1.2 第二个爬虫&#x1f6b2;1.3 第三个爬虫&#x1f388;2. 文件保存 ❤️1.4 翻页处理 &#x1f525;二、完整代码 &#x1f525;&#x1f525;&#x1f525; Pytho…

安卓手机原生运行 ARM Ubuntu 24.04 桌面版(一)

本篇文章&#xff0c;聊一聊尝试让安卓手机原生运行 Ubuntu&#xff0c;尤其是运行官方未发布过的 ARM 架构的 Ubuntu 24.04 桌面版本。 写在前面 最近的几篇文章&#xff0c;都包含了比较多的实操内容、需要反复的复现验证&#xff0c;以及大量的调试过程&#xff0c;为了不…

【C++语言】Date类的代码实现(操作符重载运用)

文章目录 前言Date类的构思Date类的相关实现基本框架&#xff08;默认成员函数&#xff09;计算n天前\后的日期补充&#xff1a;前置、后置说明判断两个日期的关系&#xff08;大于&#xff0c;小于等&#xff09;&#xff1b;可以计算两个日期之间相差多少天补充&#xff1a;流…

SQLI-labs-第十三关和第十四关

目录 第十三关 1、判断注入点 2、判断当前数据库 3、爆表名 4、爆字段名 5、爆值 第十四关 1、判断注入点 知识点&#xff1a;POST方式的单引号和括号闭合错误,报错注入 第十三关 思路&#xff1a; 1、判断注入点 使用Burpsuite抓包 首先加入一个单引号&#xff0c;…

Linux vscode push报错fatal: Authentication failed

注意啊&#xff0c;Git基于密码的身份验证已经被删除了&#xff0c;所以这个报错发生时无论密码正确与否&#xff0c;以及参考比较旧的改bug教程&#xff0c;都没法提交。进入提示的网址&#xff0c;生成个人访问令牌就好了

Java解决垂直鉴权问题(对垂直权限进行校验)

Java解决垂直鉴权问题&#xff08;对垂直权限进行校验&#xff09; 文章目录 Java解决垂直鉴权问题&#xff08;对垂直权限进行校验&#xff09;前言一、垂直鉴权是什么&#xff1f;二、实现过程1.新建接口权限菜单映射表2.项目初始化时加载接口菜单映射关系3.自定义过滤器拦截…

淘宝电商商家ERP订单接口接入指南:对接ERP与淘宝系统的数据桥梁

最近几年&#xff0c;电商发展如火如荼&#xff0c;一方面互联网企业在推互联网 和O2O&#xff0c;同时很多传统企业也在积极互联网&#xff0c;通过各种电商平台拓展销售渠道&#xff0c;有些还同时建有自建的电商平台。这些电商平台通常下单&#xff0c;结算&#xff0c;促销…

大数据技术原理与技术简答

1、HDFS中名称节点的启动过程 名称节点在启动时&#xff0c;会将FsImage 的内容加载到内存当中&#xff0c;此时fsimage是上上次关机时的状态。然后执行 EditLog 文件中的各项操作&#xff0c;使内存中的元数据保持最新。接着创建一个新的FsImage 文件和一个空的 Editlog 文件…

封装Springboot基础框架功能-03

在些模块中汇总了一些web开发常用的配置和功能。 模块源码结构 Restful API常用定义 QueryParam请求参数 Data public class QueryParam {private String key;private String value; }RestfulController实现 RestfulController.java&#xff0c;主要汇总一些常用的restful的…