C++红黑树

📟作者主页:慢热的陕西人

🌴专栏链接:C++

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

本博客主要内容讲解了红黑树并且使用红黑树模拟实现set和map

在这里插入图片描述

文章目录

  • 红黑树
    • Ⅰ.红黑树的概念
    • Ⅱ.红黑树的性质
    • Ⅲ. 红黑树节点的定义
    • Ⅳ.红黑树结构
    • Ⅴ. 红黑树的插入操作
    • Ⅵ. 红黑树的验证
    • Ⅶ.红黑树的删除
    • Ⅷ.红黑树和AVL树的比较
    • Ⅸ.红黑树的一些应用
    • Ⅹ.红黑树模拟实现STL中的map和set
      • Ⅹ.Ⅰ红黑树的迭代器
      • Ⅹ. Ⅱ红黑树的改造
      • Ⅹ. Ⅲ map的模拟实现
      • Ⅹ. Ⅳ set的模拟实现

红黑树

Ⅰ.红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。

image-20230904192915437

Ⅱ.红黑树的性质

①每个结点不是红色就是黑色

②根节点是黑色的

③如果一个节点是红色的,则它的两个孩子结点是黑色的

④ 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

⑤每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点 个数的两倍?

因为满足以上的性质我们可以得到

最短的路径: 全黑

最长的路径:一黑一红

所以最大就是最长的路径是最短路径的两倍;

Ⅲ. 红黑树节点的定义

// 节点的颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{RBTreeNode(const ValueType& data = ValueType(),Color color = RED): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _color(color){}RBTreeNode<ValueType>* _pLeft;   // 节点的左孩子RBTreeNode<ValueType>* _pRight;  // 节点的右孩子RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给
出该字段)ValueType _data;            // 节点的值域Color _color;               // 节点的颜色
};

思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?

因为红黑树有一个原则就是每条路径的黑色节点数量相同,那么当我们插入一个节点默认颜色给成黑色的时候就会造成这个原则被打破。

Ⅳ.红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了 与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft 域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:
image-20230831205219366

Ⅴ. 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

①插入新节点

bool Insert(const pair<k, v>& kv){if (_root == nullptr){_root = new node(kv);return true;}node* parent = nullptr;node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new node(kv);if (parent->_kv.first > cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){node* GrandFather = parent->_parent;if (GrandFather->_left == parent){node* uncle = GrandFather->_right;//情况1 : u存在且为红,变色处理,并且向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;GrandFather->_col = RED;//继续往上调整cur = GrandFather;parent = cur = parent;} else // 情况2 + 3:u不存在/u存在且为黑,旋转+变色{//     g//   p   u// c if (cur = parent->_left){RotateR(GrandFather);parent->_col = BLACK;GrandFather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;GrandFather->_col = RED;}break;}}else{//    g//u       p//               cnode* uncle = GrandFather->_left;//情况1 : u存在且为红,变色处理,并且向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;GrandFather->_col = RED;//继续往上调整cur = GrandFather;parent = cur = parent;}else//情况2 + 3{//    g//u       p//               cif (parent->_right == cur){RotateL(GrandFather);parent->_col = BLACK;GrandFather->_col = RED;}//     g//u         p//     celse{RotateR(parent);RotateL(GrandFather);cur->_col = BLACK;GrandFather->_col = RED;}break;}}}_root->_col = BLACK;return true;}

Ⅵ. 红黑树的验证

红黑树的检测分为两步:

①检测其是否满足二叉搜索树(中序遍历是否为有序序列)

②检测其是否满足红黑树的性质

bool IsBalance(){if (_root == nullptr){return true;}if (_root&& _root->_col = RED){cout << "根节点是红色的" << endl;}int benchmark = 0;node* cur = _root;while (cur){if (cur->_col == BLACK)benchmark++;cur = cur->_left;}return _check(_root, 0, benchmark);}bool _check(node* root, int BlackNum, int benchmark){if (root == nullptr){if (benchmark != BlackNum){cout << "黑色节点数量不相同" << endl;return false;}return true;}if (root->_col == BLACK){BlackNum++;}if (root->_col == RED && root->parent && root->parent->_col == RED){cout << "出现了连续的红色" << endl;}return _check(root->_left, BlackNum, benchmark) &&_check(root->_right, BlackNum, benchmark);}

Ⅶ.红黑树的删除

红黑树的删除本节不做讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》

http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

Ⅷ.红黑树和AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红 黑树更多。

Ⅸ.红黑树的一些应用

①C++ STL库 – map/set、mutil_map/mutil_set

②Java 库

③linux内核

④其他一些库

Ⅹ.红黑树模拟实现STL中的map和set

Ⅹ.Ⅰ红黑树的迭代器

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代 器,需要考虑以前问题:

  • begin()和end()

    STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后, 可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位 置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块? 能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行–操作,必须要能找最 后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:

    image-20230913131636135

  • operator–()和operator++()

    Self& operator++(){if (_node->_right){//右不为空找右子树的最左节点Node* subleft = _node->_right;while (subleft->_left){subleft = subleft->_left;}_node = subleft;}else{//右为空沿着根的路径,找到孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent != nullptr && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator--(){if (_node->_left){//左子树存在那么我们找到左子树的最右节点Node* subright = _node->_left;while (subright->_right){subright = subright->_right;}_node = subright;}else{//如果左子树不存在那么我们找到孩子是父节点右子树的祖先节点Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}
    

Ⅹ. Ⅱ红黑树的改造

因为mapset结构的不同,但是他们底层都是红黑树。所以我们这里利用C++泛型变成的特性擦汗给你是去改造红黑树,让他们表面看起来在用同一棵红黑树(底层实例化出了两个不同的红黑树)

// 因为关联式容器中存储的是<key, value>的键值对,因此
// k为key的类型,
// T: 如果是map,则为pair<K, V>; 如果是set,则为k
// KeyOfT: 通过value来获取key的一个仿函数类
template<class k, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> node;
public:~RBTree(){_Destroy(_root);_root = nullptr;}public:typedef __RBTreeIterator<T, T&, T*> iterator;typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
//...
//做出以上的修改那么我们还需要将insert函数进行修改
//1.首先我们修改了形参为T类型这样既可以兼容set也同时可以兼容map
//2.又因为set和map的关键值比较不同虽然pair的比较C++内部支持了但是不是我们想要的比较方式
//所以我们自行编写一个仿函数来解决这个问题
//3.再就是返回值我们返回了当前插入值的迭代器和是否插入的真值作为返回。原因是map是需要支持下标访问的
//且需要支持可以通过下标进行插入,所以我们这里选择改造insert函数来实现;
pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}node* parent = nullptr;node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}//...
}

Ⅹ. Ⅲ map的模拟实现

上面我们已经结合set和map的功能改造了红黑树,所以接下来我们只需要根据自己的需要去调用红黑树的接口即可。

namespace xupt
{template<class K, class V>class map{public://这里是仿函数的实现struct MapKeyOfT{const K&  operator()(const pair<K, V>& kv){//我们需要返回的是pair中的firstreturn kv.first;}};public://在红黑树迭代器的基础上去构造map自己的迭代器//这里的typename的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量typedef typename RBTree<K, pair<const K, V>, MapKeyOfT> :: iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){//用到insert返回值的地方pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret->first->second;}pair<iterator, bool> Insert(const pair<const K, V>& kv){return _t.Insert(kv);}private://这里pair里面的K用const进行了修饰目的是防止K被改变//和set中使用const迭代器的策略不同RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
}

Ⅹ. Ⅳ set的模拟实现

set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来

namespace xupt
{template<class K>class set{public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT> :: const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT> :: const_iterator const_iterator;pair<iterator, bool> Insert(const K& key){return _t.Insert(key);}iterator begin(){return _t.begin();}iterator end(){return _t.end();}private:RBTree<K, K, SetKeyOfT> _t;};}

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

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

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

相关文章

嵌入式学习笔记(25)串口通信的基本原理

三根通信线&#xff1a;Tx Rx GND &#xff08;1&#xff09;任何通信都要有信息作为传输载体&#xff0c;或者有线的或则无线的。 &#xff08;2&#xff09;串口通信时有线通信&#xff0c;是通过串口线来通信的。 &#xff08;3&#xff09;串口通信最少需要2根&#xff…

基于人体呼出气体的电子鼻系统的设计与实现

基于人体呼出气体的电子鼻系统的设计与实现 摘要 电子鼻技术是通过模式识别技术对传感器采集的人体呼出气体进行分类训练的方法。本文研究实现的电子鼻系统包括下面几个部分:首先搭建以Arduino为控制核心的气路采集装置&#xff0c;包括MOS传感器和双阀储气袋构建的传感器阵列和…

在网站标题中使用可以让搜索引擎更容易(识别网站的主要内容)

随着互联网的飞速发展&#xff0c;越来越多的企业开始重视网站的优化。优化网站排名不仅可以增加曝光率和点击率&#xff0c;也可以提高品牌知名度和销售额。本文将从关键字优化方案入手&#xff0c;为大家详细介绍如何提升网站排名。 什么是关键字&#xff1f; 关键字是指用…

如何在Windows 10/11中重置网络,以及重置后的注意事项有哪些

本文介绍如何在Windows 10和Windows 11中重置网络设置。 如何重置Windows 10网络设置 在Windows10中使用网络重置实用程序相当简单。 一、进入“开始”菜单>“设置”,然后选择“网络和Internet”。 二、在左侧导航窗格中,选择“状态”以确保你正在查看网络状态窗口。然…

深入解析 Nginx 代理配置:从 server 块到上游服务器的全面指南

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

使用CFimagehost源码搭建免费的PHP图片托管私人图床,无需数据库支持

文章目录 1.前言2. CFImagehost网站搭建2.1 CFImagehost下载和安装2.2 CFImagehost网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

Kubernetes(K8S)集群部署

目录 一、创建3台虚拟机 二、为每台虚拟机安装Docker 三、安装kubelet 3.1 安装要求 3.2 为每台服务器完成前置设置 3.3 为每台服务器安装kubelet、kubeadm、kubectl 四、使用kubeadm引导集群 4.1 master服务器 4.2 node1、node2服务器 4.3 初始化主节点 4.4 work节…

学习视觉SLAM需要会些什么?

前言 SLAM是现阶段很多研究生的研究方向&#xff0c;我也是作为一个即将步入视觉SLAM的研究生&#xff0c;网上对于SLAM的介绍很多&#xff0c;但很少有人完整系统的告诉你学习视觉SLAM该有那些基础&#xff0c;那么此贴将告诉你学习SLAM你要有那些方面的基础。 文章目录 前言…

【AI】机器学习——线性模型(线性回归)

线性模型既能体现出重要的基本思想&#xff0c;又能构造出功能更加强大的非线性模型 参考&#xff1a;唐宇迪机器学习课程 文章目录 3.1 线性模型3.1.1 数据3.1.2 目标/应用 3.2 线性回归3.2.1 回归模型历史3.2.2 回归分析研究内容回归分析步骤 3.2.3 回归分析分类3.2.4 回归模…

Codeforces Round 895 (Div. 3) A ~ F

Dashboard - Codeforces Round 895 (Div. 3) - Codeforces A 问多少次能使a 和 b相等&#xff0c;就是abs(a - b) / 2除c向上取整&#xff0c;也就是abs(a - b)除2c向上取整。 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #de…

学习网络编程No.6【将服务器日志和守护进程化】

引言&#xff1a; 北京时间&#xff1a;2023/9/1/21:15&#xff0c;下午刚更新完博客&#xff0c;同理再接再厉&#xff0c;这样整天不需要干什么&#xff0c;除了玩手机的日子不多了&#xff0c;马上就要开学&#xff0c;每天需要签到签退的日子就要来临&#xff0c;烦躁&…

浅谈STL|STL函数对象篇

一.函数对象概念 概念: 重载函数调用操作符的类&#xff0c;其对象常称为函数对象 函数对象使用重载的()时&#xff0c;行为类似函数调用&#xff0c;也叫仿函数 本质: 函数对象(仿函数)是一个类&#xff0c;不是一个函数 特点 函数对象在使用时&#xff0c;可以像普通函数那…

Linux提权

shell分本地shell 和 webshell 有些提权方式只能本地shell使用 常见内核漏洞查找脚本以及利用 环境变量提权 suid https://www.cnblogs.com/banglook/archive/2022/03/17/16019354.html linux特殊命令https://www.secrss.com/articles/28493 什么是suid SUID (Set UID)是Li…

「中秋来袭」没想到,用OpenCV竟能画出这么漂亮的月饼「附源码」

一、前言 中秋佳节即将来临&#xff0c;作为传统的中国节日之一&#xff0c;人们除了品尝美味的月饼、赏月外&#xff0c;还喜欢通过绘画来表达对这个节日的喜悦和祝福。而如今&#xff0c;随着科技的不断发展&#xff0c;竟然可以借助计算机视觉库OpenCV来绘制精美的月饼和可…

Redis的数据持久化方案

目录 前言 RDB方式 概述&#xff1a; 1.RDB手动 &#xff12;.RDB自动 RDB优缺点 AOF方式 概述 AOF写数据的三种策略 AOF相关配置 AOF重写 AOF重写方式 手动重写 bgrewriteaof 自动重写 总结 前言 Redis是一个内存型数据库&#xff0c;也就是说如果不将内存中的…

被删除并且被回收站清空的文件如何找回

文件的意外删除和回收站清空是许多用户面临的普遍问题。这种情况下&#xff0c;很多人会感到无助和焦虑&#xff0c;担心自己的重要文件永远丢失。然而&#xff0c;幸运的是&#xff0c;依然存在一些有效的方法能够帮助我们找回被删除并且被回收站清空的文件。 ▌被删除文件在…

uniapp——实现聊天室功能——技能提升

这里写目录标题 效果图聊天室功能代码——html部分代码——js部分代码——其他部分 首先声明一点&#xff1a;下面的内容是从一个uniapp的程序中摘录的&#xff0c;并非本人所写&#xff0c;先做记录&#xff0c;以免后续遇到相似需求抓耳挠腮。 效果图 聊天室功能 发送图片 …

进制转换问题

进制 二进制 &#xff08;Binary&#xff09;&#xff1a;0、1。简写为B 八进制&#xff08;Octonary&#xff09;&#xff1a;0、1、2、3、4、5、6、7。简写为O 十进制&#xff08;decimalism&#xff09;&#xff1a;0、1、2、3、4、5、6、7、8、9 简写为D 十六进制&#xff…

【建议收藏】职场人口头和书面沟通必备词语,瞬间高大上

这年头&#xff0c;在职场不但要会做&#xff0c;还要会说。 会说还不能平铺直叙的说&#xff0c;还要能把普通的工作说出话来&#xff0c;这就需要一些“考究”的用词。尤其是在某些头部企业的带领下&#xff0c;业务不够、产品不行、解决方案不够新&#xff0c;就用华丽的辞…

ASEMI二极管1N4148(T4)的用途和使用建议

编辑-Z 二极管是一种常见的电子元件&#xff0c;其中1N4148&#xff08;T4&#xff09;是一款广泛使用的快恢复二极管。它具有快速的开关特性和高反向阻挡能力&#xff0c;适用于多种电子应用。本文将介绍1N4148&#xff08;T4&#xff09;的特点、用途和如何正确使用该二极管…