文章目录

  • 1. 认识红黑树
    • 1.1 红黑树的规则
    • 1.2 红黑树如何确保最长路径不超过最短路径的2倍呢?
    • 1.3 红黑树的效率
  • 2. 实现红黑树
    • 2.1 红黑树的结构
    • 2.2 红黑树的插入
      • 2.2.1 第一种情况:插入节点的父节点和其uncle节点都为红色,且uncle节点存在
      • 2.2.2 第2种情况:插入节点cur和其父节点为红,其uncle节点不存在或者为黑色
      • 2.2.3 第3种情况:uncle节点不存在或者为黑色,且cur节点和parent节点为红
    • 2.3 红黑树的查找
    • 2.4 红黑树的验证
  • 3. 代码

在这里插入图片描述
前面我们学过了控制二叉搜索树平衡的AVL树,今天我们就来学习控制二叉搜索树平衡的另一种—红黑树

1. 认识红黑树

  • 红黑树(Red-Black Tree)是一种自平衡的二叉查找树(BST),通过特定的规则和旋转操作维持树的平衡,从而保证高效的查找、插入和删除操作。它的核心用途是在动态数据集中提供对数时间复杂度(O(logN))的搜索、插入和删除性能,尤其适用于需要频繁修改且要求高效查询的场景
  • 红黑树是⼀棵二叉搜索树,他的每个结点增加一个存储位来表示结点的颜色,可以是红色或者黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的

1.1 红黑树的规则

  1. 每个节点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,那么它的两个孩子节点必须是黑色的,也就是说任意一条路径不会有连续的红色节点
  4. 对于任意一条路径,从该节点到其所有NULL节点的简单路径上,均包含相同数量的黑色节点
    在这里插入图片描述

在这里插入图片描述


1.2 红黑树如何确保最长路径不超过最短路径的2倍呢?

  • 由规则4可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径就就是全是黑色结点的路径,假设最短路径长度为bh(black height)
  • 由规则2和规则3可知,任意一条路径不会有连续的红色结点,所以极端场景下,最长的路径就是一黑一红间隔组成,那么最长路径的长度为2*bh
  • 综合红黑树的4点规则而言,理论上的全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都存在的。假设任意一条从根到NULL结点路径的长度为x,那么bh <= x <= 2*bh

1.3 红黑树的效率

在这里插入图片描述


  • 假设N是红黑树树中结点数量,h最短路径的长度,那么 , 由此推出h ≈ logN,也就是意味着红黑树增删查改最坏也就是走最长路径 ,那么时间复杂度还是O(logN)
  • 红黑树的表达相对AVL树要抽象⼀些,AVL树通过高度差直观的控制了平衡。红黑树通过4条规则的颜色约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对而言,插入相同数量的节点,红黑树的旋转次数是更少的,因为他对平衡的控制没那么严格
  • 最差全黑

在这里插入图片描述


2. 实现红黑树

2.1 红黑树的结构

在这里插入图片描述


2.2 红黑树的插入

  • 1. 红黑树插入一个值按照二叉搜索树的方式插入,插入后只需要观察是否满足红黑树的4条规则
  • 2. 如果是空树插入,那么新插入的节点要给黑色。但如果是非空树插入,那么新插入的节点则必须是红色,因为如果是黑色,就破坏了红黑树任意路径黑色节点相同的规则
  • 3.非空树插入红色节点后,如果它的父节点是黑色的,那么满足4条规则,插入结束
  • 4.如果非空树插入红色节点后,其父节点是红色的,则违反了红黑树任意一条路径中不能出现连续红色节点的规则,得进一步分析

在这里插入图片描述


在这里插入图片描述


2.2.1 第一种情况:插入节点的父节点和其uncle节点都为红色,且uncle节点存在

在这里插入图片描述


  • 注意:上述图中只是最简单的一种,无非就是将cur的父节点parent和uncle节点变色(红→黑),再将grandfather节点变色(黑→红)
  • 另外cur节点无论插在parent的左边还是右边,都满足上述的变色逻辑
  • 当然上图中这颗树可能也只是棵子树,需要继续向上更新,那什么时候向上更新呢?
  • 向上更新的条件: g节点的父节点是红色时,p、u、g节点变色后需继续向上更新
  • 为什么说g节点的父节点是红色就得继续更新呢?因为g节点变完色后是红色,红黑树不允许出现连续的红色节点
  • 向上更新步骤:p、u、g节点变完色后,直接让cur等于g节点,然后p、u、g节点随着cur节点更新而更新,直到g节点的父节点为黑,插入结束
  • 当然,如果grandfather就是根节点,那么p、u、g节点变完色后,直接让g节点重新变为黑色,插入结束

在这里插入图片描述


在这里插入图片描述


  • 代码实现

在这里插入图片描述


2.2.2 第2种情况:插入节点cur和其父节点为红,其uncle节点不存在或者为黑色

  • 这种情况一般是:如果parent是grandfather左子树节点,cur节点也是p左孩子的情况;或者parent是grandfather右子树节点,cur节点也是p右孩子的情况
  • 因此第2种情况就是:cur节点和parent节点是它们的父节点的左孩子/右孩子方向是保持一致的
  • 对于uncle节点不存在的情况,则是直接以节点g为旋转点进行右单旋,然后分别让p节点变红,g节点变黑

在这里插入图片描述


  • 对于uncle节点存在且为黑色的情况,处理情况相同,同样是以g节点为旋转点进行右单旋,然后p节点为红,g节点为黑

在这里插入图片描述


  • 代码实现

在这里插入图片描述


2.2.3 第3种情况:uncle节点不存在或者为黑色,且cur节点和parent节点为红

  • 这种情况一般是:如果parent是grandfather左子树节点,cur节点是p右孩子的情况;或者parent是grandfather右子树节点,cur节点是p左孩子的情况
  • 因此第2种情况就是:cur节点和parent节点是它们的父节点的左孩子/右孩子方向不保持一致
  • 第3种情况和第2种情况类似,只是单纯一次单旋满足不了红黑树的要求,需要双旋+变色

在这里插入图片描述


在这里插入图片描述


  • 同样和第2种情况类似,情景1:uncle节点不存在

在这里插入图片描述


  • 情景2:uncle节点存在且为黑色

在这里插入图片描述


  • 代码实现

在这里插入图片描述


  • 当然,对于左右单旋的代码在上一节AVL树有详细讲解,这里不过多赘述

2.3 红黑树的查找

  • 红黑树的查找按照二叉搜索树的方式查找就行,效率O(logN)
//查找
Node* Find(const K& key)
{Node* cur = _root;if (key > cur->_kv.first)cur = cur->_right;else if (key < cur->_kv.first)cur = cur->_left;elsereturn cur;return nullptr;
}

2.4 红黑树的验证

  • 这里获取最长路径和最短路径,检查最长路径不超过最短路径的2倍是不可行的,因为就算满足这个条件,红黑树也可能颜色不满足规则,当前暂时没出问题,后续继续插入还是会出问题的。所以我们还是去检查4点规则,满足这4点规则,一定能保证最长路径不超过最短路径的2倍
  • 1. 规则1枚举颜色类型,天然实现保证了颜色不是黑色就是红色
  • 2. 规则2直接检查根即可
  • 3. 规则3前序遍历检查,遇到红色结点查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲的颜色就方便多了
  • 4. 规则4前序遍历,遍历过程中用形参记录跟到当前结点的blackNum(黑色结点数量),前序遍历遇到黑色结点就++blackNum,走到空就计算出了一条路径的黑色结点数量。再任意一条路径黑色结点数量作为参考值,依次比较即可

在这里插入图片描述


  • 代码实现

在这里插入图片描述


3. 代码

  • RBTree.h代码
#pragma once
#include <iostream>
using namespace std;//枚举常量
enum Colour
{RED,BLACK
};//红黑树节点
template <class K,class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K,V> kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_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;}//如果是非空树 Node* parent = nullptr;Node* cur = _root;//寻找插入位置while (cur){//插入值比cur节点值大if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}//插入值比cur节点值小else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}elsereturn false;}//插入节点cur = new Node(kv);cur->_col = RED;//如果插入值大于parent值if (kv.first > parent->_kv.first)parent->_right = cur;//如果插入值小于parent值else if (kv.first < parent->_kv.first)parent->_left = cur;//存储cur节点的父节点cur->_parent = parent;//定义cur节点的p、u、g节点 ---> 更新while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//如果parent为grandfather的左孩子if (parent == grandfather->_left){Node* uncle = grandfather->_right;//第一种情况:父亲、叔叔都为红,爷爷为黑if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}//第2、3种情况else{//第2种情况 ---> uncle节点不存在或者为黑色//      g//   p     u//c 右单旋if(cur == parent->_left){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//第3种情况 ---> uncle节点不存在或者为黑色//      g//   p     u//      c  左单旋 + 右单旋else{RotateL(parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}//如果parent为grandfather的右孩子else{Node* uncle = grandfather->_left;//第一种情况:父亲、叔叔都为红,爷爷为黑if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}//第2、3种情况else{//第2种情况 ---> uncle节点不存在或者为黑色//      g//   u     p//左单旋       cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//第3种情况 ---> uncle节点不存在或者为黑色//      g//   u     p//      c  右单旋 + 左单旋else{RotateR(parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}}//直接赋予根节点黑色_root->_col = BLACK;return true;}//左单旋void RotateL(Node* parent){//定义两个节点指针Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL)subRL->_parent = parent;Node* parent_Parent = parent->_parent;parent->_parent = subR;//如果parent为根节点if (parent_Parent == nullptr){_root = subR;subR->_parent = nullptr;}//如果parent不是根节点else{//如果旋转之前parent所在的整个子树为根节点的左子树if (parent_Parent->_left == parent)parent_Parent->_left = subR;elseparent_Parent->_right = subR;subR->_parent = parent_Parent;}}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR)subLR->_parent = parent;Node* parent_Parent = parent->_parent;parent->_parent = subL;//如果parent为根节点if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果parent不是根节点else{if (parent_Parent->_left == parent)parent_Parent->_left = subL;elseparent_Parent->_right = subL;subL->_parent = parent_Parent;}}//中序遍历输出AVL树数据void Inorder(){_Inorder(_root);cout << endl;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << "-" << root->_kv.second << " ";_Inorder(root->_right);}//红黑树节点个数size_t Size(){return _Size(_root);}size_t _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}//查找Node* Find(const K& key){Node* cur = _root;while(cur){if (key > cur->_kv.first)cur = cur->_right;else if (key < cur->_kv.first)cur = cur->_left;elsereturn cur;}return nullptr;}//前序遍历查找每条路径黑色节点个数bool Check(Node* root, int Count_BlackNode, int retNum){if (root == nullptr){if (Count_BlackNode != retNum){cout << "存在黑色节点数量不等的路径" << endl;return false;}return true;}//root节点非空 ---> 检查两个孩子节点不方便,可能还有孩子不存在,直接检查其父节点if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续的红色节点" << endl;return false;}//如果节点为黑色if (root->_col == BLACK){++Count_BlackNode;}return Check(root->_left, Count_BlackNode, retNum) && Check(root->_right, Count_BlackNode, retNum);}//检查是否满足平衡bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED){cout << "根节点为红色!" << endl;return false;}//记录一条路径上黑色节点数量int retNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++retNum;}cur = cur->_left;}return Check(_root, 0, retNum);}private:Node* _root = nullptr;
};
  • 测试代码test.cpp
#include"RBTree.h"
#include<vector>
//AVL树的平衡测试
void RBTreetest1()
{RBTree<int, int> rb1;//常规测试//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//for (auto& e : a)//{//	rb1.insert({ e,e });//}//带有双旋的测试用例int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto& e : a){rb1.insert({ e,e });}rb1.Inorder();cout << "Size:" << rb1.Size() << endl;
}void RBTreetest2()
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(i + rand());}size_t begin2 = clock();RBTree<int, int> t;for (size_t i = 0; i < v.size(); ++i){t.insert(make_pair(v[i], v[i]));}size_t end2 = clock();cout << "Insert:" << end2 - begin2 << endl;cout << "Size:" << t.Size() << endl;t.insert({ 20,20 });cout << t.Find(20) << endl;if (t.IsBalance())cout << "该二叉树是红黑树!" << endl;elsecout << "该二叉树不是红黑树!" << endl;}int main()
{//RBTreetest1();RBTreetest2();return 0;
}

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

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

相关文章

解决 SQL 错误 [1055]:深入理解 only_full_group_by 模式下的查询规范

在日常的 SQL 开发中&#xff0c;你是否遇到过这样的报错&#xff1a;SQL 错误 [1055] [42000]: Expression #N of SELECT list is not in GROUP BY clause and contains nonaggregated column...&#xff1f;尤其是在 MySQL 5.7 及以上版本中&#xff0c;这个错误更为常见。本…

Keepalived 原理及配置(高可用)

一、Keepalived 原理keepalived 基于 VRRP&#xff08;虚拟路由冗余协议&#xff09;实现高可用。核心原理是通过竞选机制在多台服务器&#xff08;主 / 备节点&#xff09;中选举出一台主节点承担服务&#xff0c;同时备节点持续监控主节点状态&#xff1a;主节点正常时&#…

从代码混乱到井然有序:飞算JavaAI的智能治理之道

文章目录一、前言二、飞算JavaAI平台三、飞算JavaAI安装流程3.1 Idea安装配置3.2 官网注册登入四、飞算JavaAI独特魅力:合并项目场景4.1 ERP老项目精准翻新&#xff1a;保留核心逻辑的智能改造方案4.2 智能合并&#xff1a;重构ERP系统的代码迷宫4.3 ERP接口智能导航&#xff1…

iOS打开开发者模式

启用开发者模式的方法在iOS设备上启用开发者模式通常需要连接Xcode或通过设置手动开启&#xff0c;以下是具体步骤&#xff1a;通过Xcode启用将iOS设备通过USB线连接到Mac电脑。打开Xcode&#xff08;需提前安装&#xff09;。在Xcode的菜单栏中选择 Window > Devices and S…

leetcode101.对称二叉树树(递归练习题)

文章目录一、 题目描述二、 核心思路&#xff1a;判断左右子树是否互为镜像三、 递归的终止条件 (Base Cases)四、 代码实现与深度解析五、 关键点与复杂度分析六、 总结与对比 (LC100 vs LC101)LeetCode 101. 对称二叉树 - 力扣【难度&#xff1a;简单&#xff1b;通过率&…

【国内电子数据取证厂商龙信科技】谁是躲在“向日葵”后的

一、前言大家可能每天都在使用在远控软件&#xff0c;我们在享受远控软件带来的便利同时&#xff0c;犯罪者也在使用远控软件进行违法犯罪活动&#xff0c;以达到隐藏自己的目的。市面上常用的远控软件有“向日葵”、“TeamViewer”。二、案件背景在一次电信诈骗案件支援中&…

SAP-PP-MRPLIST

MRP(物料需求计划)分析功能,主要包含以下要点: 程序通过选择工厂和物料/销售订单范围作为输入条件,支持两种展示方式:ALV表格和树形结构 核心功能包括: 物料主数据查询(MAKT/MARA表) 销售订单数据查询(VBAP表) BOM展开(CS_BOM_EXPL_MAT_V2函数) MRP数据获取(MA…

MIT线性代数01_方程组的几何解释

Linear Algebra Lecture #1 W. Gilbert Strangn linear equations, n unknowns row picturecol pictureMatrix form {2x−y0−x2y3 \left\{\begin{matrix} 2x - y 0 \\ -x 2y 3 \end{matrix}\right. {2x−y0−x2y3​ 1 Row Picture2 Column PictureWhat are all combination…

FreeRTOS-中断管理

学习内容中断概念中断是计算机系统中一种重要的事件驱动机制&#xff0c;用于在特定条件下打断正在执行的程序&#xff0c;并跳转到预定义的中断处理程序中执行特定的操作。当发生中断时&#xff0c;处理器会立即中止当前正在执行的指令&#xff0c;保存当前的执行状态&#xf…

图像梯度处理与边缘检测

在图像处理的世界里&#xff0c;我们常常需要从复杂的像素矩阵中提取有意义的信息 —— 比如一张照片中物体的轮廓、医学影像中病灶的边界、自动驾驶视野里的道路边缘。这些 “边界” 或 “轮廓” 在专业术语中被称为 “边缘”&#xff0c;而捕捉边缘的核心技术&#xff0c;离不…

GPU服务器与PC 集群(PC农场):科技算力双子星

在数字经济高速发展的今天&#xff0c;算力已成为驱动科技创新与产业变革的核心引擎。GPU服务器凭借其强大的并行计算能力&#xff0c;在图形渲染、人工智能训练等领域展现出不可替代的优势&#xff1b;而PC集群则通过分布式架构&#xff0c;以高性价比和灵活扩展特性&#xff…

秋招Day19 - 分布式 - 分布式锁

单体时代&#xff0c;可以直接用本地锁来实现对竞争资源的加锁&#xff0c;分布式环境下就要用到分布式锁了有哪些分布式锁的实现方案&#xff1f;MySQL分布式锁、Zookeeper分布式锁、Redis分布式锁MySQL分布式锁如何实现&#xff1f;创建一张锁表&#xff0c;对字段定义唯一性…

AIStarter平台亮点解析:从ComfyUI项目上架到一键运行的完整指南

大家好&#xff01;今天分享一个AIStarter平台的深度体验&#xff0c;带你了解如何通过这个平台轻松上架和运行AI项目&#xff01;视频中&#xff0c;博主在凌晨分享了AIStarter的强大功能&#xff0c;重点展示了ComfyUI 4.0和5.0整合包的上架过程&#xff0c;以及如何简化AI项…

电脑录屏软件推荐:如何使用oCam录制游戏、教程视频

在工作、学习或游戏过程中&#xff0c;我们经常需要录制电脑屏幕&#xff0c;比如制作教程视频、记录游戏操作、分享软件使用过程等。oCam 是一款功能强大且操作简单的屏幕录制工具&#xff0c;支持 Windows 系统&#xff0c;深受用户喜爱。今天简鹿办公就来手把手教你如何使用…

安装cuml报错

安装命令 &#xff08;注意cuda的版本&#xff09; pip install --no-cache-dir --extra-index-urlhttps://pypi.nvidia.com cuml-cu11 报错&#xff1a; 找了很多网上的教程 1.版本问题 没解决 pip install --upgrade pip pip install --upgrade setuptools 2.参考下面博…

【ECharts✨】解决Vue 中 v-show 导致组件 ECharts 样式异常问题

解决Vue 中 v-show 导致组件 ECharts 样式异常问题 问题概述 在使用 Vue 的 v-show 指令实现 <PageOne/>、<PageTwo/>、<PageThree/> 三个视图的定时切换时&#xff0c;<PageTwo/> 显示时出现了异常&#xff0c;具体表现为 ECharts 图表渲染图表尺寸异…

旅游管理虚拟仿真实训室:重构实践教学新生态

在旅游产业数字化转型与教育信息化深度融合的背景下&#xff0c;旅游管理虚拟仿真实训室成为连接理论教学与行业实践的关键纽带。它通过沉浸式技术还原旅游场景&#xff0c;解决传统实训中资源受限、风险较高、时空局限等问题&#xff0c;为旅游管理专业人才培养提供全新路径。…

【在线五子棋对战】十、对战玩家匹配管理模块

文章目录前言Ⅰ. 匹配队列实现Ⅱ. 匹配队列管理类实现完整代码前言 五子棋对战的玩家匹配是根据自己的天梯分数进行匹配的&#xff0c;而服务器中将玩家天梯分数分为三个档次&#xff1a; 青铜&#xff1a;天梯分数小于 2000 分白银&#xff1a;天梯分数介于 2000~3000 分之间…

k8s之ingress定义https访问方式

接上文&#xff1a;https://blog.csdn.net/soso678/article/details/149607069?spm1001.2014.3001.5502定义后端应用与service [rootmaster ingress]# cat my-nginx.yml apiVersion: apps/v1 kind: Deployment metadata:name: my-nginx spec:selector:matchLabels:run: my-n…

《C++ vector 完全指南:vector的模拟实现》

《C vector 完全指南&#xff1a;vector的模拟实现》 文章目录《C vector 完全指南&#xff1a;vector的模拟实现》一、定义vector的成员变量二、用vector实现动态二维数组三、vector的接口实现1.vector的默认成员函数&#xff08;1&#xff09;构造函数实现&#xff08;2&…