【c++】红黑树的部分构建

news/2025/10/26 15:48:34/文章来源:https://www.cnblogs.com/yxysuanfa/p/19166928

hello~ 很高兴见到大家! 这次带来的是C++中关于红黑树这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?
个 人 主 页: 默|笙

在这里插入图片描述


文章目录

  • 一、红黑树介绍
    • 1. 红黑树的概念
    • 2. 红黑树的规则
  • 二. 红黑树的结构
  • 三. 插入
    • 1. 非空树插入会碰到的三种情况
      • 1. 1 仅变色(针对uncle为红色)
      • 1. 2 变色 + 旋转(针对uncle为空或黑色节点)
        • 变色 + 右单旋
      • 变色 + 左单旋
      • 变色 + 左右双旋
      • 变色 + 右左双旋
  • 三、红黑树的检测
  • 四、源代码


一、红黑树介绍

1. 红黑树的概念

  1. 红黑树是一棵二叉搜索树,它的每一个节点相对于普通的二叉搜索树都增加了一个成员变量来存储节点的颜色,有红色和黑色两种;然后跟AVL树一样,拥有除_left和_right之外的第三个指向该节点父节点的_parent指针。它通过控制每一条从叶子到跟节点的颜色,来确保没有一条路径会比最短路径长出2倍,它是接近平衡的
  2. 相对于AVL树,它对于平衡度的要求会稍低一些,也就是它旋转的次数会少一些,效率也会高一些,倘若红黑树的节点个数为N,最短路径为h,那么有2^h - 1 < N < 2^2h - 1。意味着红黑树即便是走最坏情况增删查改时间复杂度是2*logN,即O(logN)。

在这里插入图片描述

2. 红黑树的规则

  1. 每个节点不是黑色就是红色。
  2. 根节点必须是黑色。
  3. 如果一个节点的颜色是红色,那么它的两个孩子必须得是黑色的。也就是说一棵红黑树里面每条路径上不会出现连续2个节点都是红色的情况
  4. 对于任意节点,从它到其所有的NULL节点的简单路径上,每一条路上的黑色节点个数都必须是相同的
  1. 通过对规则的分析,不难发现红黑树是如何去控制节点颜色来达到控制每一条路径的长度的目的:在极端条件下,最短路径就是节点全为黑色的路径,最长路径是红色节点和黑色节点交错出现(一黑一红间隔)的路径。若最短路径为bh(black height),最长路径就为2*bh。
  2. 虽然理论上红黑树可以达到的最短路径是bh和最长路径2bh,但在实际应用中它们并不是存在于每一棵红黑树里的,假设红黑树的某条路径长为x,那么有bh <= x <= 2bh。
  3. 根节点必须为黑色和规则3保证了红色节点绝对不会比黑色节点多。
  • 有的书里面如《算法导论》里补充了一条每个叶子节点(NIL)都是黑色的规则。这里的叶子节点非我们所熟知的那个叶子节点,而是空节点,有些书籍上面也把NIL叫做外部节点。NIL是为了方便准确的标识出所有路径。

在这里插入图片描述

二. 红黑树的结构

enum Colour
{
RED,
BLACK
};
template<class K, class V>struct RBTreeNode{RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){ };pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col = RED;};template<class K, class V>class RBTree{typedef RBTreeNode<K, V> Node;public:private:Node* _root = nullptr;};
  1. 跟AVL树的节点结构相比,它没有平衡因子变量,而是有用来存储节点颜色的变量。<AVL博客>
  2. 我们使用枚举类型来存储节点颜色,这样既增加代码可读性,也能避免无效值。
  3. 每一个新插入的节点默认都是红色节点,因为不能破坏规则4相对于破坏规则3之后需要做出的处理太多,太复杂。

三. 插入

  1. 它的插入规则跟二叉搜索树一样,只是多了一个_parent指针需要处理,这里就不再做讲解。<二叉搜索树博客>
bool Insert(const pair<K, V>& kv){//处理空树的情况if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;}//找到要插入的位置else{Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_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;return true;
  1. 根节点的颜色做一下特殊处理,因为根节点一定得是黑色,节点初始化默认为红色
  2. 接下来对上面的插入代码的基础之上做一些改变。

1. 非空树插入会碰到的三种情况

  1. 非空树插入一个红色节点,它的父节点可能是红色,也有可能是黑色,如果是黑色节点,我们直接插入就行,插入结束。

  2. 但如果是红色节点,为了不违反红黑树的第三个规则,我们必须得做一些变换。 分析可以得知,该节点是红色节点,记为cur,cur的父节点也是红色节点,将其父节点记为parent,parent的父节点一定是黑色节点,将其父节点记为grandfather。这三个颜色是固定的,但是parent的兄弟节点的颜色是不定的,将其兄弟节点记为uncle。接下来我们会先根据兄弟节点的情况来做出不同的措施来符合规则三。就比如下图:
    在这里插入图片描述

  3. 其兄弟节点有三种情况:红色节点,黑色节点,以及空节点。如果是红色节点,我们只需要进行变色处理;但如果是黑色节点或者是空节点,我们必须进行变色+旋转处理。

接下来cur简记为c,parent简记为p,grandfather简记为g,uncle简记为u。

1. 1 仅变色(针对uncle为红色)

在这里插入图片描述

  1. 现在的cur不一定是新插入的节点,它有可能原来是黑色的节点,不过因为下面方框里面节点的更新变成了红色。
  2. 从插入的节点到根节点这一路上,可能会有反反复复的更新,直到不再有连续的红色节点。就比如说变色这个过程,我们可以看到g节点变成了红色,如果它不是根节点,那么它的父节点一定是红色,那么它会是新的cur节点,并根据新的uncle的情况向上更新。
  3. 我们可以看到,我们将节点8和节点15的颜色从红色变成了黑色,将节点10的颜色从黑色变成了红色。能不能只把节点8和节点15的颜色变成黑色呢?这样似乎也能完美符合规则。
  4. 从节点10开始到下面NULL节点的每一条路径上,黑色节点的个数都是相同的。我们变色一定是不能够改变每一条路径上面黑色节点的数量的。只把节点8和节点15变成黑色肯定不行,这样从节点10到下面NULL节点的每一条路上就凭空多出了一个黑色节点。如果节点10是根节点那肯定没问题,但是,万一这只是某棵树的子树呢?
  5. 变色的过程可以总结为:将p和u节点变成黑色,将g节点变成红色。然后g节点变成新的cur节点,接着更新其他节点。然后将根节点变成黑色,毕竟根节点有可能在更新的过程中变成红色,这个可以放到所有更新的最后,不用每次更新完都去管一下_root的颜色。

1. 2 变色 + 旋转(针对uncle为空或黑色节点)

旋转具体实现过程看<AVL树博客>

变色 + 右单旋

当p为g的左孩子且cur为p的左孩子时,右单旋。

在这里插入图片描述

  1. 若方框里的黑色节点数量都为0,那么节点4是新插入的节点,u为空,不会存在更新过程。若不为0,那么一定存在从下往上的更新与变色过程。之后是统一的左单旋,然后将节点8的颜色变为黑色,节点10的颜色变为红色。
  2. 针对uncle为空或为黑色节点的情况,仅变色一定是行不通的,因为我们必须得遵循规则4。所以必须进行旋转操作。
  3. 根节点由节点10变成了节点8,由于节点10为黑色,节点8为红色,我们若将节点8的颜色改为红色,那么就能停止更新的过程,不再继续向上更新。这就是为什么不通过只改变节点4的颜色来达成目的的原因。
  4. 所以变色+右单旋的过程就是:右单旋之后将p改为黑色,g改为红色。
  5. 右单旋直接用AVL里删去平衡因子改变之后的代码就行。

变色 + 左单旋

当p为g的右孩子且cur为p的右孩子时,左单旋。

在这里插入图片描述

  1. 这里省略插入节点之后变色的步骤。左单旋以及下面的旋转不再详细讲解。
  2. 和右单旋一样,旋转之后将p变成黑色,将g变成红色

变色 + 左右双旋

p是g的左孩子且cur是p的右孩子时,进行左右双旋。

在这里插入图片描述

  1. 就是先进行一次左旋转,再进行一次右旋转。节点颜色变化跟单旋不一样,是cur变成黑色,g变成红色。

变色 + 右左双旋

当p为g的右孩子且cur为p的左孩子时,右左双旋。

在这里插入图片描述

  1. 就是先进行一次右旋转,再进行一次左旋转。节点颜色变化跟单旋不一样,是cur变成黑色,g变成红色。

插入完整代码:

bool Insert(const pair<K, V>& kv){//处理空树的情况if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}//找到要插入的位置else{Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_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;//父节点为红色的情况下需要进行处理if (parent->_col == RED){while (parent && parent->_col == RED){//记录节点Node* grandfather = parent->_parent;Node* uncle = nullptr;if (grandfather->_left == parent){uncle = grandfather->_right;}else{uncle = grandfather->_left;}//uncle为红色的情况//仅变色if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//更新cur = grandfather;parent = cur->_parent;}//uncle为黑色或为空的情况else{//右旋转if (grandfather->_left == parent && parent->_left == cur){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;break;}//左旋转else if (grandfather->_right == parent && parent->_right == cur){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;break;}//左右双旋else if (grandfather->_left == parent && parent->_right == cur){RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;break;}//右左双旋else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;break;}}}}}//处理根节点颜色_root->_col = BLACK;return true;}

三、红黑树的检测

  1. 检查一棵树是否为红黑树主要是检查一棵树有没有满足红黑树的那四个规则:
  1. 颜色不是黑就是红
  2. 根节点是黑
  3. 从根节点到NULL的路径上没有相邻的红色节点
  4. 从任意节点到其他NULL的路径上黑色节点数量都是相同的。
  1. 第一个规则不需要验证,颜色只可能是红色或黑色;第二个规则很好验证,看_root是否是黑色就行。我们来看第三个规则,要验证有没有相邻的红色节点,就需要遍历这整棵树,又因为一个节点能有两个孩子,验证难度比较大,而一个节点只能有一个父节点,我们通过验证其父节点和它颜色是否相同来验证规则3。
  2. 最难验证的就是规则4,我们需要计算黑色节点数量,增加一个传递黑色节点数量的参数BlackNum传递黑色节点数量,遇到黑色节点BlackNum++。以及需要一个用来比较的黑色节点个数的参考值leftMost(最左边那一条路径上的黑色节点数量)
  3. 节点为空的时候,就把BlackNum的值拿来跟参考值进行比较就行。

红黑树检测代码:

bool IsBalance()
{
if (_root->_col == RED)
{
return false;
}
int leftMost = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
leftMost++;
}
cur = cur->_left;
}
return _check(_root, 0, leftMost);
}
bool _check(Node* cur, int BlackNum, const int leftMost)
{
if (cur == nullptr)
{
if (BlackNum != leftMost)
{
return false;
cout << "黑色节点的数量不相等" << endl;
}
else
return true;
}
if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED)
{
cout << cur->_kv.first << "->" << "连续的红色节点" << endl;return false;}if (cur->_col == BLACK){BlackNum++;}return _check(cur->_left, BlackNum, leftMost) && _check(cur->_right, BlackNum, leftMost);}

四、源代码

#pragma once
#include<iostream>using namespace std;enum Colour{RED,BLACK};template<class K, class V>struct RBTreeNode{RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){ };pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col = RED;};template<class K, class V>class RBTree{typedef RBTreeNode<K, V> Node;public:bool Insert(const pair<K, V>& kv){//处理空树的情况if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}//找到要插入的位置else{Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_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;//父节点为红色的情况下需要进行处理if (parent->_col == RED){while (parent && parent->_col == RED){//记录节点Node* grandfather = parent->_parent;Node* uncle = nullptr;if (grandfather->_left == parent){uncle = grandfather->_right;}else{uncle = grandfather->_left;}//uncle为红色的情况//仅变色if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//更新cur = grandfather;parent = cur->_parent;}//uncle为黑色或为空的情况else{//右旋转if (grandfather->_left == parent && parent->_left == cur){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;break;}//左旋转else if (grandfather->_right == parent && parent->_right == cur){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;break;}//左右双旋else if (grandfather->_left == parent && parent->_right == cur){RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;break;}//右左双旋else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;break;}}}}}//处理根节点颜色_root->_col = BLACK;return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root->_col == RED){return false;}int leftMost = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){leftMost++;}cur = cur->_left;}return _check(_root, 0, leftMost);}int Height(){return _Height(_root);}int Size(){return _Size(_root);}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}private:int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool _check(Node* cur, int BlackNum, const int leftMost){if (cur == nullptr){if (BlackNum != leftMost)return false;elsereturn true;}if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED){return false;}if (cur->_col == BLACK){BlackNum++;}return _check(cur->_left, BlackNum, leftMost) && _check(cur->_right, BlackNum, leftMost);}void _InOrder(const Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}void RotateR(Node* parent){//记录节点Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;//改变指针parent->_left = subLR;subL->_right = parent;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}//避免subLR = nullptr出现空指针解引用的情况if (subLR){subLR->_parent = parent;}parent->_parent = subL;}void RotateL(Node* parent){//记录节点Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;//改变指针指向subR->_left = parent;parent->_right = subRL;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}if (subRL){subRL->_parent = parent;}parent->_parent = subR;}Node* _root = nullptr;};

今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~
让我们共同努力, 一起走下去!

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

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

相关文章

域登录态分享(类sso)

遇到一个问题,在同一主域下的多个子域之间共享登录状态的需求。例如:main.example.com 主站 learn.example.com 学习中心希望在任意子域登录后,其他子域也能自动识别登录状态,包括登出同步。Cookie 跨子域共享 浏览…

ssh原理

ssh原理 密码认证原理服务器生成一对公私钥,认证流程客户端发送请求给服务器服务器生成了一对公私钥,发送公钥给客户端客户端使用这个公钥进行加密,发送给服务端服务端通过私钥进行解密,认证成功后,就能连接上去了…

实现一个简易版本的IOC

简单的IOC其实就是一个map,key是对象名字,value是对象的实例,Spring容器初始化的时候会将配置文件或注解信息转换成BeanDefinition对象存储在集合中,然后遍历集合通过反射实例化Bean,实例化后的Bean会放入到名为si…

Alibaba Cloud Linux 4 安装docker后,修复docker的方法

Alibaba Cloud Linux 4 安装docker后,修复docker的方法#!/bin/bashset -e echo "🔧 Docker 服务崩溃修复..." # 停止所有 Docker 相关服务echo "🛑 停止 Docker 服务..."sudo systemctl stop …

MPK(Mirage Persistent Kernel)源码笔记(2)--- 多层结构化图模型

MPK(Mirage Persistent Kernel)源码笔记(2)--- 多层结构化图模型 目录MPK(Mirage Persistent Kernel)源码笔记(2)--- 多层结构化图模型0x00 概要0x01 机制1.1 当前问题1.2 解决方案1.2.1 μGraphs:多层次计算…

day000 ML串讲

numpy模块numpy模块的主要作用:numpy表示一维或者多维的数组(容器),主要是用来存储和运算数值型数据的。常用属性:shape:返回数组的形状 ndim:返回数组的维度 size:返回数组元素的个数 dtype:返回数组元素的数…

我的学习方式破局思考 ——读《认真听讲》、《做中学》与《做教练》有感

坦白说,在点击那三篇链接之前,我以为这又是一次“形式大于内容”的任务。但读完的那一刻,我感到脸上发烫,心中却豁然开朗。这三篇文章,像三位来自不同角度的导师,共同对我陈旧的学习观念进行了一次彻底的“手术”…

cmd运行python文件

C:\Users\Air>python F:\blogs\鱼书\man.py Initialized Hello David! Good-bye David!

Unity协程除了实现功能还可以增加可读性

协程作为异步、延时等待。还可以作为Update的解耦已读方案。 以一个UI淡进淡出为例。 不是用协程时,Lerp的更新都包含在Update方法中:1 1 using UnityEngine;2 2 using UnityEngine.UI;3 3 4 4 namespace UI.Mai…

2025年TPU厂家权威推荐榜:专业TPU加纤、TPU改性生产技术实力与市场口碑深度解析

2025年TPU厂家权威推荐榜:专业TPU加纤、TPU改性生产技术实力与市场口碑深度解析 随着全球高分子材料行业的快速发展,热塑性聚氨酯弹性体(TPU)作为一种性能优异的高分子材料,在汽车制造、电子电器、医疗器械、运动…

Nginx程序结构及核心配置

Nginx程序结构及核心配置1. 程序结构 $ sudo rpm -ql nginx /etc/logrotate.d/nginx # 配置日志切割策略 /etc/nginx # nginx 主程序存放路径 /etc/nginx/conf.d # nginx 配置文件目录 /etc/nginx/conf.d/default.conf…

事倍功半是蠢蛋57 typora相对路径图片上传到github

3️⃣ Typora 中设置图片自动复制(推荐) 打开 Typora → 文件(File) → 偏好设置(Preferences) → 图像(Images): 插入图片时:选择 “复制图片到指定路径” 指定路径:./images 插入时使用:相对路径 以后你在 Typ…

Nginx部署星益小游戏平台(静态页面)

Nginx部署星益小游戏平台(静态页面)1. 星益小游戏平台部署 源码下载:https://c1026.dmpdmp.com/e01306e2fdcd92aac114784572ec6e20/68a424b6/2019/08/17/f1aac2083a4d2e76196e57e68aed173f.zip?fn=%E6%98%9F%E7%9B…

hadoop应用遇到的问题

当遇到如下问题,大致是安装包有问题,需要重新下载安装包,但是直接用命令行下载需要十个小时,这边在widows上下载好在传送到虚拟机 接着按照以下步骤 结合你当前hdfs命令功能受限的问题,以下是彻底修复Hadoop环境的…

企业级Nginx安装部署

企业级Nginx安装部署1. Nginx 安装方式编译安装:使用源码,根据企业业务需求,需要什么功能,编译时添加什么模块,但是安装过程较为繁琐 自动化安装:配置好安装源,直接通过自动化工具安装即可 二进制安装:解压即用…

2025 年 10 月门窗十大品牌综合实力权威推荐榜单,聚焦产能、专利与环保的实力品牌深度解析

2025 年 10 月门窗十大品牌综合实力权威推荐榜单由中国建筑金属结构协会、全国工商联家具装饰业商会联合发布。本次榜单以 “产能保障 + 专利技术 + 环保标准” 为核心评估体系,突破传统单一性能评选局限:产能维度考…

以“听”为基,以“做”为翼

原文:Scalers:大学生上课为什么一定要认真听讲? https://www.scalerstalk.com/816-attention 读《大学生上课为什么一定要认真听讲?》,我对课堂学习的重要性有了更为深刻的体悟。 课堂是知识传递的核心阵地,老师…

序列密码基本模型

随机序列 基本概念 https://www.cnblogs.com/luminescence/p/18938331 最终周期序列 设\(\{s_i\}^{\infty}_{i=1}\)是一条序列,假如存在正整数\(t\)和\(r\),使得对于任意\(i>=t\)都有\(s_{i+r} = s_i\) 则最小整数…

企业级Web应用及Nginx介绍

企业级Web应用及Nginx介绍1. Nginx 简介 Nginx("engine x")是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。其将源代码以…

2025 年 10 月门窗十大品牌综合实力权威推荐榜单,精准检测与稳定性能兼具的行业优选解析

2025 年 10 月门窗十大品牌综合实力权威推荐榜单由中国建筑金属结构协会、全国工商联家具装饰业商会联合发布。本次榜单以 “精准检测认证 + 长期稳定性能” 为核心评估逻辑,突破传统评选单一性:检测维度严格执行《铝…