【C++模拟实现】手撕红黑树(含图解)

【C++模拟实现】手撕红黑树(含图解)

目录

  • 【C++模拟实现】手撕红黑树(含图解)
      • 红黑树的介绍(百度百科)
        • 简介
        • 特征(十分重要,红黑树的基础)
      • 红黑树的实现代码(insert部分)
      • 验证是否为红黑树
      • 红黑树的图解
        • 红黑树的正常情况:![请添加图片描述](https://img-blog.csdnimg.cn/8a5fabb884354c178c158e0b9ff8129c.jpeg)
        • 红黑树情况一(uncle节点为红色):
        • 红黑树情况二(需要旋转的情况,uncle节点为黑色或者为空)

作者:爱写代码的刚子

时间:2023.9.11

前言:这次的博客除了红黑树的模拟实现外还附加了图示来帮助理解红黑树,以及编写代码对红黑树的验证。

红黑树的介绍(百度百科)

简介

  • 红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。

  • 红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。

  • 由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。

特征(十分重要,红黑树的基础)

红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

**性质1. 结点是红色或黑色。 **

**性质2. 根结点是黑色。 **

性质3. 所有叶子都是黑色。(叶子是NIL结点)

性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)

性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

是性质4导致路径上不能有两个连续的红色结点确保了这个结果。最短的可能路径都是黑色结点,最长的可能路径有交替的红色和黑色结点。因为根据性质5所有最长的路径都有相同数目的黑色结点,这就表明了没有路径能多于任何其他路径的两倍长。

因为红黑树是一种特化的二叉查找树,所以红黑树上的只读操作与普通二叉查找树相同。

红黑树的实现代码(insert部分)

#include <iostream>
using namespace std;
enum Colour
{BLACK,RED
};template<class K,class V>
struct RBTreeNode
{RBTreeNode(const pair<K,V>& kv):_left(nullptr),_parent(nullptr),_right(nullptr),_c(RED)//初始为红节点,_kv(kv){}RBTreeNode* _left;RBTreeNode* _parent;RBTreeNode* _right;Colour _c; pair<K,V> _kv;};template<class K,class V>
class RBTree
{typedef RBTreeNode<K,V> Node;
public:RBTree():_root(nullptr){}bool insert(const pair<K,V>& kv){if(_root==nullptr){_root=new Node(kv);_root->_c=BLACK;return true;}else{Node* cur=_root;Node* parent=nullptr;while(cur){if(cur->_kv.first>kv.first){parent=cur;cur=cur->_left;}else if(cur->_kv.first<kv.first){parent=cur;cur=cur->_right;}else{return false;}}cur=new Node(kv);if(parent->_kv.first>kv.first){parent->_left=cur;}else{parent->_right=cur;}cur->_parent=parent;//需要调整的情况while(parent&&parent->_c==RED){Node* grandfather=parent->_parent;if(grandfather->_left==parent){Node* uncle=grandfather->_right;//判断uncle的几种情况if(uncle&&uncle->_c==RED){uncle->_c=parent->_c=BLACK;grandfather->_c=RED;cur=grandfather;parent=cur->_parent;}else {if(cur==parent->_left){_RotateR(grandfather);grandfather->_c=RED;parent->_c=BLACK;}else{_RotateL(parent);_RotateR(grandfather);grandfather->_c=RED;cur->_c=BLACK;}break;}}else{Node* uncle=grandfather->_left;if(uncle&&uncle->_c==RED){uncle->_c=parent->_c=BLACK;grandfather->_c=RED;cur=grandfather;parent=cur->_parent;}else {if(cur==parent->_right){_RotateL(grandfather);parent->_c=BLACK;grandfather->_c=RED;}else{_RotateR(parent);_RotateL(grandfather);cur->_c=BLACK;grandfather->_c=RED;}break;}}}}_root->_c=BLACK;//头节点始终为黑色return true;}void _RotateR(Node* parent){Node*cur=parent->_left;Node*curRight=cur->_right;Node*ppnode=parent->_parent;cur->_right=parent;parent->_left=curRight;if(curRight){curRight->_parent=parent;}parent->_parent=cur;//处理ppnodeif(parent==_root){_root=cur;cur->_parent=nullptr;}else{if(ppnode->_left==parent){ppnode->_left=cur;}else{ppnode->_right=cur;}cur->_parent=ppnode;}}void _RotateL(Node* parent){Node* cur=parent->_right;Node* curLeft=cur->_left;Node* ppnode=parent->_parent;cur->_left=parent;parent->_right=curLeft;if(curLeft){curLeft->_parent=cur;}parent->_parent=cur;if(parent==_root){_root=cur;cur->_parent=nullptr;}else{if(ppnode->_left==parent){ppnode->_left=cur;}else{ppnode->_right=cur;}cur->_parent=ppnode;}}private:Node* _root;
};

验证是否为红黑树

bool CheckColour(Node* root, int blacknum, int benchmark)//并不是最优递归
{if (root == nullptr){if (blacknum != benchmark)return false;return true;}if (root->_c == BLACK){++blacknum;}if (root->_c == RED && root->_parent && root->_parent->_c == RED){cout << root->_kv.first << "有连续红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);
}
bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;if (root->_c != BLACK){return false;}// 基准值int benchmark = 0;Node* cur = _root;while (cur){if (cur->_c == BLACK)++benchmark;cur = cur->_left;}return CheckColour(root, 0, benchmark);}
  • 添加以上成员函数可以来验证该红黑树的黑节点数量是否正确,以及是否出现连续红色节点。
  • 以下为测试代码:请添加图片描述

红黑树的图解

红黑树的正常情况:请添加图片描述

红黑树情况一(uncle节点为红色):

请添加图片描述

  • 由于不能出现连续的红色节点,所以我们需要进行处理,此时我们有以下结论:

    当parent为RED节点时说明我们插入了一个节点(红黑树默认插入RED节点)导致出现了连续的红色节点

    当uncle节点存在且为红,我们需要将parent和uncle节点变为黑色,将grandfather变为红色

    最后将grandfather赋值给cur,parent指向cur的_parent,进入下一个循环,直到parent为黑色节点

红黑树情况二(需要旋转的情况,uncle节点为黑色或者为空)

  1. parent为grandfather的左节点,且cur为parent的左节点(右单旋

    请添加图片描述

  2. parent为grandfather的右节点,且cur为parent的右节点(左单旋

原理和右单旋几乎相同,图略。

  1. parent为grandfather的右节点,且cur为parent的左节点(右左双旋

    请添加图片描述

  2. parent为grandfather的左节点,且cur为parent的右节点(左右双旋

原理和右左双旋几乎相同,图略。


附:

  • 降序插入构建红黑树动图:

请添加图片描述

  • 左右旋转的成员函数复用了avl树的旋转函数。
  • 左右旋转图解:请添加图片描述

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

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

相关文章

C++之weak_ptr与shared_ptr智能指针实例(一百九十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

美陆军推动人工智能算法的持续更新

源自&#xff1a;蓝德智库 声明:公众号转载的文章及图片出于非商业性的教育和科研目的供大家参考和探讨&#xff0c;并不意味着支持其观点或证实其内容的真实性。版权归原作者所有&#xff0c;如转载稿涉及版权等问题&#xff0c;请立即联系我们删除。 “人工智能技术与咨询”…

C++(day5)

思维导图 小练习 实现一个图形类&#xff08;Shape&#xff09;&#xff0c;包含受保护成员属性&#xff1a;周长、面积&#xff0c;公共成员函数&#xff1a;特殊成员函数书写 定义一个圆形类&#xff08;Circle&#xff09;&#xff0c;继承自图形类&#xff0c;包含私有属性…

攻防世界-WEB-easyupload

1.新建.user.ini文件&#xff0c;内容如下 GIF89a auto_prepend_filea.jpg 2.上传该文件&#xff0c;并用burp抓包&#xff0c;将Content-Type: application/octet-stream修改为 Content-Type: image/jpg 3.放包&#xff0c;结果如下 4. 新建a.txt文件&#xff0c;内容为 GIF89…

窗口函数-分组排序:row_number()、rank() 、dense_rank()、ntile()

窗口函数语法结构&#xff1a; 分析函数() over(partition by 分组列名 order by 排序列名 rows between 开始位置 and 结束位置) 开窗函数和聚合函数区别&#xff1a; 聚合函数会对一组值进行计算并返回一个值&#xff0c;常见的比如sum()&#xff0c;count()&#xff0c;ma…

Autojs 小游戏实践-潮玩宇宙开扭蛋

概述 最近在玩潮流宇宙&#xff0c;里面有扭蛋兔的一个玩法&#xff0c;开始有很多蛋&#xff0c;需要我们一个个点开&#xff0c;然后根据装备品质替换分解&#xff0c;潮流提供了自动开扭蛋功能&#xff0c;但是开到品质比自己装备好的时候回暂停&#xff0c;由于个人懒得看…

Java密码学之数字签名

密码系统是加密技术及其附带基础工具的实现&#xff0c;以提供信息安全服务。基本密码系统的各种组件是明文&#xff0c;加密算法&#xff0c;密文&#xff0c;解密算法&#xff0c;加密密钥和解密密钥。其中加密密钥和解密密钥是&#xff1a; 加密密钥是发件人已知的值。发送…

《向量数据库指南》——向量数据库内核面临的技术挑战及应对措施

最近一年&#xff0c;以 ChatGPT、LLaMA 为代表的大语言模型的兴起&#xff0c;将向量数据库的发展推向了新的高度。 向量数据库是一种在机器学习和人工智能领域日益流行的新型数据库&#xff0c;它能够帮助支持基于神经网络而不是关键字的新型搜索引擎。向量数据库不同于传统的…

CMake+CLion+Qt配置

在这里我下载MSVC的工具包&#xff0c;并没有下载Visual Studio。 配置编译环境 下载Visual Studio&#xff0c;其中有MSVC编译工具&#xff0c;下载MSVC工具包&#xff0c; 工具包下载链接&#xff1a;https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/ …

第5篇 vue的通信框架axios和ui框架-element-ui以及node.js

一 axios的使用 1.1 介绍以及作用 axios是独立于vue的一个项目&#xff0c;基于promise用于浏览器和node.js的http客户端。 在浏览器中可以帮助我们完成 ajax请求的发送在node.js中可以向远程接口发送请求 1.2 案例使用axios实现前后端数据交互 1.后端代码 2.前端代码 &…

Linux 下 C语言版本的线程池

目录 1. 线程池引入 2. 线程池介绍 3. 线程池的组成 4. 任务队列 5. 线程池定义 6. 头文件声明 7. 函数实现 8. 测试代码 1. 线程池引入 我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a;如果并发的线程数…

循环购模式:一种新型的电商模式,让你的生意更轻松

你是否想过&#xff0c;你的商品能够以一种更高效、更环保、更有趣的方式销售给顾客&#xff1f;你是否想过&#xff0c;你的顾客能够以一种更低成本、更高回报、更多互动的方式享受你的商品&#xff1f;如果你的答案是肯定的&#xff0c;那么你一定要了解一下循环购模式。 循环…