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…

Flask 使用 JWT(一)

下面是一些 JWT 的使用场景: 1、 授权:这是 JWT 最常的使用场景。一旦用户登录,后续的每个请求都必须携带 JWT ,允许用户携带 Token 访问所有的路由、服务器和资源。单点登录时目前使用最广泛的一个场景,因为它开销小并且能够轻易的实现跨域访问。 2、信息交换:JWT Token…

什么是数据安全和数据加密的关键技术和算法

1、什么是数据安全和数据加密的关键技术和算法。 数据安全和数据加密是保障数据完整性和机密性的重要手段。以下是数据安全和数据加密的关键技术和算法&#xff1a; 散列函数&#xff08;Hash Function&#xff09;&#xff1a;散列函数是一种将任意长度的消息映射为固定长度…

【Python小练习】使用Python编写POC 脚本-上篇

文章目录 基本概念什么是Poc什么是Exp 常见问题Exp 和 Poc相同&#xff1f;为什么网上大多漏洞仅公开POC&#xff1f;为什么使用Python来编写 编写流程获取详情搭建环境复现漏洞编写脚本测试脚本 基本概念 什么是Poc Poc&#xff08;全称: Proof Of Concept&#xff09;, 中文…

conda与pip镜像源环境配置

文章目录 一. 参考二. conda三. pip 一. 参考 b站环境配置视频 校园网镜像站 二. conda 利用校园网镜像站, 找到conda的镜像源配置文档. 将下面的文档复制到电脑上的.condarc文件中. channels:- defaults show_channel_urls: true default_channels:- https://mirrors.tuna…

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

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

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

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

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

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

每日一题 78子集(模板)

题目 78 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[1],[2]…

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

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

【数据仓库基础(四)】数据仓库需求:基本需求和数据需求

文章目录 一. 基本需求1. 安全性2. 可访问性3. 自动化 三. 数据需求1. 准确性2&#xff0e;时效性3&#xff0e;历史可追溯性 从基本需求和数据需求两方面介绍对数据仓库系统的整体要求。 一. 基本需求 1. 安全性 数据仓库中含有机密和敏感的数据。为了能够使用这些数据&…

使用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.公网访问测…

centos下安装docker

centos下安装docker 1、删除历史安装包 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine2、配置yum源 sudo yum install -y yum-utils sudo yum-config-manager \ …

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节…

【计算机网络】网络编程接口 Socket API 解读(1)

Socket 是网络协议栈暴露给编程人员的 API&#xff0c;相比复杂的计算机网络协议&#xff0c;API 对关键操作和配置数据进行了抽象&#xff0c;简化了程序编程。 本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍&#xff0c;从而更好的理解 socket 编程。…

学习视觉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 回归模…

【Golang入门】Golang第一天心得

生活所迫&#xff0c;入门一下Go 很奇葩的第一点&#xff0c;接口 package mainimport ("fmt" )// 定义一个接口 type Shape interface {Area() float64 }// 定义一个矩形类型 type Rectangle struct {Width float64Height float64 }// 矩形类型实现Shape接口的Ar…

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;烦躁&…