红黑树(C++)

文章目录

  • 写在前面
  • 1. 红黑树的概念及性质
    • 1. 1 红黑树的概念
    • 1. 2 红黑树的性质
  • 2. 红黑树节点的定义
  • 3. 红黑树的插入
    • 3.1 按照二叉搜索的树规则插入新节点
    • 3.2 检测新节点插入后,红黑树的性质是否造到破坏
  • 4.红黑树的删除
  • 5.红黑树的验证
  • 6.源码

写在前面

在上篇文章中,我们实现了AVL树,AVL树是一种高度平衡的二叉搜索树。通过确保任何节点的左右子树的高度差不超过1,AVL树能够维持严格的平衡状态。然而,严格平衡的代价是某些插入和删除操作可能需要多次旋转

本篇文章将实现红黑树,它是一种近似平衡的二叉搜索树。红黑树通过维持某些性质来保持树的平衡。通过这些性质,红黑树确保从根到叶子的所有路径中,最长路径不超过最短路径的两倍,从而实现近似平衡这种近似平衡比AVL树的严格平衡稍松一些,使得它在某些插入和删除操作中的效率更高,因为它需要的旋转次数较少

在接下来的内容中,我们将详细介绍红黑树的实现过程,以及它是如何通过旋转和重新着色来维护红黑树的平衡性。

1. 红黑树的概念及性质

1. 1 红黑树的概念

红黑树,是一种二叉搜索树其每个节点存储一个额外的颜色位,用于确保树的平衡性。在红黑树中,节点的颜色可以是红色或黑色通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
在这里插入图片描述

1. 2 红黑树的性质

红黑树通过以下性质来保持树的平衡:

  1. 每个节点是红色或黑色

  2. 根节点是黑色

  3. 每个叶子节点(NIL节点)是黑色
    ps:NIL节点不是传统意义上的叶子节点,而是指空节点,是用来方便我们来数路径的
    在这里插入图片描述

  4. 如果一个节点是红色,则它的两个子节点都是黑色不能出现连续的红色节点)。

  5. 从根节点到其每个叶子节点的所有路径都包含相同数量的黑色节点

为什么满足上面的性质,红黑树就能保证其最长路径中节点个数不会超过最短路径节点个数的两倍?
分析如下:
在这里插入图片描述

2. 红黑树节点的定义

TreeNode成员的几点解释:

  1. 枚举类型 Color 用于表示红黑树节点的颜色。红黑树节点只能是红色或黑色。
  2. TreeNode 是一个模板结构体,用于存储红黑树节点的信息。
    _left 指向左孩子节点,_right 指向右孩子节点,_parent 指向父节点。
    _kv 存储节点的键值对,类型为 pair<K, V>,其中 K 是键的类型,V 是值的类型。
    _col 存储节点的颜色,类型为 Color。
    构造函数初始化节点的左孩子、右孩子和父节点为空指针,键值对通过参数传递进来,并将节点颜色初始化为红色(新插入的节点默认是红色)。
enum Color
{RED,BLACK
};
template<class K, class V>
struct TreeNode
{TreeNode* _left; // 左孩子节点TreeNode* _right;// 右孩子节点TreeNode* _parent;// 父节点pair<K, V> _kv;// 存储键值对Color _col;  // 节点颜色// 构造函数TreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)// 新插入的节点初始为红色{}
};

3. 红黑树的插入

红黑树就是在二叉搜索树的基础上增加了节点的颜色来控制平衡,因此红黑树也可以看成是二叉搜索树。那么红黑树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点。
  2. 检测新节点插入后,红黑树的性质是否造到破坏。

具体操作步骤如下:

3.1 按照二叉搜索的树规则插入新节点

具体操作过程参考之前写的文章,这里就不在赘述,详情参考之前写的这篇文章:搜索二叉树,这里直接给出插入新节点的具体代码。

bool insert(const pair<K, V>& kv)
{//空树if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}//找插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}//插入cur = new Node(kv);if (kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}else{cur->_parent = parent;parent->_left = cur;}
}

3.2 检测新节点插入后,红黑树的性质是否造到破坏

这里涉及的旋转操作参考之前写的文章AVL树(C++),里面详细介绍了旋转操作,这里就不在赘述。
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整
注意:下面图片中的树有可能是一颗完整的树,也有可能是一颗局部的子树。
在这里插入图片描述

当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
ps:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点。

  • 情况一: cur为红,p为红,g为黑,u存在且为红
    在这里插入图片描述
    ps:cur可能是新插入的节点,也有可能是在下面插入节点,不断往上调整,使得cur的颜色变红。

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
在这里插入图片描述

ps:如果g是根节点,调整完成后,需要将g改为黑色;
如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整。

在这里插入图片描述

  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
    解决方式:
    cur是p的左孩子,以g为旋转点右单旋,g变红,p变黑,调整结束,无需向上调整;
    cur是p的右孩子,先以p为旋转点左单旋,再以g为旋转点右单旋,g变红,p变黑,调整结束,无需向上调整。

    在这里插入图片描述

1. 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
在这里插入图片描述
2. 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。
在这里插入图片描述
p有可能是g的右孩子,这种情况下旋转操作与上面的情况相反,变色操作还是一样的,这里就不在赘述了。
如下图:
在这里插入图片描述
检测新节点插入后,红黑树的性质是否造到破坏的具体代码如下:

//插入以后判断是否满足红黑树的规则
//不满足则调整
while (parent && parent->_col == RED)
{Node* grandfather = parent->_parent;//p是g的左孩子if (parent == grandfather->_left){Node* uncle = grandfather->_right;//u存在且为红if (uncle && uncle->_col == RED){//变色+向上调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = grandfather->_parent;}else{//不存在或者存在且为黑//旋转+变色//cur 是p的左 以g为旋转点右单旋if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//cur 是p的右 先以p为旋转点左单旋,再以g为旋转点右单旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else//p是g的右孩子{Node* uncle = grandfather->_left;//u存在且为红if (uncle && uncle->_col == RED){//变色+向上调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = grandfather->_parent;}else{//不存在或者存在且为黑//旋转+变色//cur 是p的右 以g为旋转点左单旋if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//cur 是p的左 先以p为旋转点右单旋,再以g为旋转点左单旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}
}
_root->_col = BLACK;

4.红黑树的删除

红黑树的删除和AVL树一样不做深入研究,实现红黑树的插入已经足够帮助我们来了解其是如何控制平衡的,关于红黑树的删除有兴趣的可参考:《算法导论》或者《STL源码剖析》。

5.红黑树的验证

红黑树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证红黑树,可以分两步:

  1. 检测其是否满足二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。
    代码如下:
void _InOrder(Node* root)
{if (root == nullptr) return;_InOrder(root->_left);cout << root->_kv.first << ' ';_InOrder(root->_right);
}
void InOrder()
{_InOrder(_root);cout << endl;
}
  1. 检测其是否满足红黑树的性质
    不能出现连续的红色节点;每条路径上的黑色节点数量相同。
bool _IsRbTree(Node* root, int blacknum, int num)
{if (root == nullptr){//判断当前路径和最左路径上的黑色节点数量是否相同if (blacknum != num)return false;return true;}//当前节点为黑色,当前路径上的黑色节点数量加1if (root->_col == BLACK) num++;Node* parent = root->_parent;//判断是否出现连续的红色节点if (root->_col == RED && parent->_col == RED){cout << "出现连续的红色节点";return false;}//递归去判断其左右子树是否满足性质return _IsRbTree(root->_left, blacknum, num)&& _IsRbTree(root->_right, blacknum, num);
}
bool IsRbTree()
{//空树是红黑树if (_root == nullptr) return true;if (_root->_col == RED) return false;//检查根节点的颜色//统计最左路径黑色节点的数量,以其为参考值,去判断每条路径上的黑色节点数量是否相同int blacknum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){blacknum++;}cur = cur->_left;}return _IsRbTree(_root, blacknum, 0);
}

验证用例:
常规场景1 :{16, 3, 7, 11, 9, 26, 18, 14, 15}。
在这里插入图片描述
特殊场景2:{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}。
在这里插入图片描述
场景三:随机生成N个数字插入验证,这里给出代码,有兴趣的可以自己去测试一下。

int main()
{const int N = 1000;srand((unsigned int)time(nullptr));RBTree<int, int> rbTree;for (int i = 0; i < N; ++i){int num = rand() + i;rbTree.insert(make_pair(num, num));}//rbTree.InOrder();cout << rbTree.IsRbTree();return 0;
}

6.源码

#include <iostream>
using namespace std;
enum Color
{RED,BLACK
};
template<class K, class V>
struct TreeNode
{TreeNode* _left;TreeNode* _right;TreeNode* _parent;pair<K, V> _kv;Color _col;TreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}
};template<class K, class V>
class RBTree
{typedef TreeNode<K, V> Node;
public:bool insert(const pair<K, V>& kv){//空树if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}//找插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}//插入cur = new Node(kv);if (kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}else{cur->_parent = parent;parent->_left = cur;}//插入以后判断是否满足红黑树的规则//不满足则调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = grandfather->_parent;}else{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{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = grandfather->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsRbTree(){if (_root == nullptr) return true;if (_root->_col == RED) return false;int blacknum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){blacknum++;}cur = cur->_left;}return _IsRbTree(_root, blacknum, 0);}
private:bool _IsRbTree(Node* root, int blacknum, int num){if (root == nullptr){if (blacknum != num)return false;return true;}if (root->_col == BLACK) num++;Node* parent = root->_parent;if (root->_col == RED && parent->_col == RED){cout << "出现连续的红色节点";return false;}return _IsRbTree(root->_left, blacknum, num)&& _IsRbTree(root->_right, blacknum, num);}void _InOrder(Node* root){if (root == nullptr) return;_InOrder(root->_left);cout << root->_kv.first << ' ';_InOrder(root->_right);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//旋转,sub的右子树做parent的左子树//parent做subL的右子树parent->_right = subRL;if (subRL){subRL->_parent = parent;}Node* pParent = parent->_parent;subR->_left = parent;parent->_parent = subR;//parent为根节点,需要将根节点更新为subR if (parent == _root){_root = subR;subR->_parent = nullptr;}else{//更新subR的父节点指针if (subR->_kv.first > pParent->_kv.first){pParent->_right = subR;}else{pParent->_left = subR;}subR->_parent = pParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR){subLR->_parent = parent;}Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (subL->_kv.first > pParent->_kv.first){pParent->_right = subL;}else{pParent->_left = subL;}subL->_parent = pParent;}}
private:Node* _root = nullptr;
};#include "RBTree.h"
int main()
{RBTree<int, int> rbTree;int nums[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : nums){rbTree.insert(make_pair(e, e));}rbTree.InOrder();cout << rbTree.IsRbTree() << endl;return 0;
}

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

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

相关文章

5.3.1_2 二叉树的层次遍历

&#x1f44b; Hi, I’m Beast Cheng&#x1f440; I’m interested in photography, hiking, landscape…&#x1f331; I’m currently learning python, javascript, kotlin…&#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以订…

c++模板模式

文章目录 模板模式什么是模板模式为什么使用模板模式模板模式实现步骤 示例模板模式优缺点 模板模式 什么是模板模式 模板模式&#xff08;Template Method Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一个操作中的算法骨架&#xff0c;将某些步骤的具体实现延…

[DDR4] DDR 简史

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解DDR4》 存和硬盘&#xff0c;这对电脑的左膀右臂&#xff0c;共同扛起了存储的重任。内存以其超凡的存取速度闻名&#xff0c;但一旦断电&#xff0c;内存中的数据也会消失。它就像我们的工作桌面&…

tokenization(二)子词切分方法

文章目录 概述BPE构建词表词元化代码实现 WordPieceUnigram估算概率&#xff08;E&#xff09;删除词元&#xff08;M&#xff09; 参考资料 概述 接上回&#xff0c;子词词元化&#xff08;Subwords tokenization&#xff09;是平衡字符级别和词级别的一种方法&#xff0c;也…

网络通信架构

BS架构/CS架构 使用协议分别对应&#xff1a; TCP / HTTP 在计算机网络和软件开发中&#xff0c;CS架构&#xff08;Client-Server Architecture&#xff0c;客户端-服务器架构&#xff09;和BS架构&#xff08;Browser-Server Architecture&#xff0c;浏览器-服务器架构&am…

云和运维(SRE)的半生缘-深读实证02

这个标题不算太夸张&#xff0c;云计算和很多IT岗位都有缘&#xff0c;但是和运维&#xff08;SRE&#xff09;岗位的缘分最深。 “深读实证”系列文章都会结合一些外部事件&#xff0c;点明分析《云计算行业进阶指南》书中的内容。本次分享介绍了下列内容&#xff1a; 我以运维…

Matlab电话按键拨号器设计

前言 这篇文章是目前最详细的 Matlab 电话按键拨号器设计开源教程。如果您在做课程设计或实验时需要参考本文章&#xff0c;请注意避免与他人重复&#xff0c;小心撞车。博主做这个也是因为实验所需&#xff0c;我在这方面只是初学者&#xff0c;但实际上&#xff0c;从完全不…

USB2.0高速转接芯片CH347应用开发手册

CH347应用开发手册 V1.3 一、简介 CH347是一款USB2.0高速转接芯片&#xff0c;以实现USB-UART(HID串口/VCP串口)、USB-SPI、USB-I2C、USB-JTAG以及USB-GPIO等接口&#xff0c;分别包含在芯片的四种工作模式中。 CH347DLL用于为CH347芯片提供操作系统端的UART/SPI/I2C/JTAG/B…

Linux_应用篇(17) FrameBuffer 应用编程

本章学习 Linux 下的 Framebuffer 应用编程&#xff0c; 通过对本章内容的学习&#xff0c; 大家将会了解到 Framebuffer 设备究竟是什么&#xff1f;以及如何编写应用程序来操控 FrameBuffer 设备。 本章将会讨论如下主题。 ⚫ 什么是 Framebuffer 设备&#xff1f; ⚫ LCD 显…

N32G031 ADC初始化

目录 1. ADC初始化概述 2. ADC初始化详细步骤 2.1 ADC配置 2.2 ADC初始化函数调用 2.3 DMA配置&#xff08;可选&#xff09; 3. 初始化结果验证 4. 注意事项 ADC采样注意事项 1. ADC初始化概述 在N32G031单片机中&#xff0c;ADC的初始化是确保ADC模块能够正常工作的…

安卓在Fragment控制状态栏显示隐藏

废话不多上效果 隐藏 显示 核心代码 首先是Framgrent package com.zx.tab;import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button;impor…

【redis】Redis的经典使用场景

目录 1.最常见——缓存2.数据共享分布式3.分布式锁4.全局ID5.计数器6.限流7.位统计8.购物车9.用户消息时间线timeline10.消息队列11.抽奖点赞、签到、打卡13.商品标签14.商品筛选15.用户关注、推荐模型16排行榜 1.最常见——缓存 数据类型&#xff1a;string例如&#xff1a;热…

给Windows软件添加异常捕获模块生成dump文件(附源码)

软件在运行过程中会时常发生内存越界、内存访问为例、stack overflow线程栈溢出、空指针与野指针等异常崩溃,仅仅是依靠Debug和Release下的调试是远远不够的,因为有些崩溃不是必现的,或者是Debug下很难出现的。所以我们需要在软件中添加异常捕获的模块,在捕获到异常时生成包…

C 语言连接MySQL 数据库

前提条件 本机安装MySQL 8 数据库 整体步骤 第一步&#xff1a;开启Windows 子系统安装Ubuntu 22.04.4&#xff0c;安装MySQL 数据库第三方库执行 如下命令&#xff1a; sudo aptitude install libmysqlclient-dev wz2012LAPTOP-8R0KHL88:/mnt/e/vsCode/cpro$ sudo aptit…

鸿蒙求职面试内容总结——6月3日ZR的FS项目

最近接到了一些公司的入职面试邀约&#xff0c;这里略去公司的和项目的名字&#xff0c;做一些整理分享。 一、长列表如何实现部分渲染&#xff0c;使用的是哪一个API 在鸿蒙系统中&#xff0c;可以使用List组件来实现长列表的部分渲染。List组件支持使用条件渲染、循环渲染、…

docker一些常用命令以及镜像构建完后部署到K8s上

docker一些常用命令以及镜像构建完后部署到K8s上 1.创建文件夹2.删除文件3.复制现有文件内容到新建文件4.打开某个文件5.查看文件列表6.解压文件&#xff08;tar格式&#xff09;7.解压镜像8.查看镜像9.删除镜像10.查看容器11.删除容器12.停止运行容器13.构建镜像14.启动容器15…

英伟达开源最强通用模型Nemotron-4 340B

英伟达的通用大模型 Nemotron&#xff0c;开源了最新的 3400 亿参数版本。 本周五&#xff0c;英伟达宣布推出 Nemotron-4 340B。它包含一系列开放模型&#xff0c;开发人员可以使用这些模型生成合成数据&#xff0c;用于训练大语言模型&#xff08;LLM&#xff09;&#xff0…

分布式系统中的经典思想实验——两将军问题和拜占庭将军问题

文章目录 一、两将军问题1.1 问题描述1.2 深入理解两将军问题1.3 实验结论 二、拜占庭将军问题2.1 问题描述2.2 深入理解拜占庭将军问题2.3 解决方案 三、两将军和拜占庭问题的关系3.1 区别和联系3.2 应用与现实意义 参考资料 一、两将军问题 1.1 问题描述 两将军问题描述的是…

el-pagination 切换分页条数,会出现两次请求

文章目录 前言一、问题展示二、源码展示 前言 继上一次发现el-pagination在删除的时候pageNum不更新的问题。这次又发现了&#xff0c;切换分页条数&#xff0c;会出现两次请求。网上有很多解决方案&#xff0c;我就不多说了&#xff0c;我就简单记一下为啥会出现两次请求的问…

21. 第21章 算法分析

21. 算法分析 这个附录选自OReilly Media出版的Alen B.Downey的Think Complexity(2012)一书. 当你读完本书之后, 可能会像继续读读那本书.算法分析是计算机科学的一个分支, 研究算法的性能, 尤其是他们的运行时间和空间需求. 参见http://en.wikipedia.org/wiki/Analysis_of_al…