当前位置: 首页 > news >正文

【高阶数据结构】红黑树

目录

  • 1. 什么是红黑树
  • 2. 红黑树的模拟实现
    • 2.1 插入
      • 2.1.1 情况1:叔叔节点U是红色
      • 2.1.2 情况2:叔叔节点U是黑色或为空(NIL)
        • (1)情况2a:直线型 (LL / RR)
        • (2)情况2b:折线型 (LR / RL)
      • 2.1.3 代码实现
    • 2.2 IsBalance
    • 2.3 Height
  • 3. 红黑树与AVL树的对比
  • 4. 源代码

1. 什么是红黑树

红黑树并不是一个完全平衡的二叉树(像AVL树那样),而是一种近似平衡的二叉搜索树。它通过一套简单的规则和调整操作,确保树的高度始终维持在 O(log n) 级别,从而保证了搜索、插入、删除等操作的高效性。

二叉树高度:[log n,n-1]
平衡二叉树 / AVL 树、 红黑树高度:O(log n)

它通过以下5条规则来维持这种平衡:

  1. 每个节点不是红色就是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,则它的两个子节点都是黑色的。(即:不能有连续的红色节点
  4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
  5. 每个叶子节点(NIL节点,空节点)都是黑色的。(此规则在实现中通常作为隐含条件)

规则3和规则4是重中之重,插入操作的所有调整都是为了维护这两条规则。
其实可以看出,这5条规则导致:红黑树最长路径最多是最短路径的两倍。

2. 红黑树的模拟实现

2.1 插入

插入操作的整体思路:

插入新节点时,我们遵循一个核心原则:先按二叉搜索树规则插入,再通过调整来恢复红黑树性质。

为什么新插入的节点默认为红色?

  • 如果插入黑色节点,会立即违反规则4,因为它所在路径的黑色节点数会增加1,修复起来非常困难,需要调整整棵树。
  • 如果插入红色节点,可能违反规则3(如果父节点也是红色),但不会违反规则4。违反规则3是一个局部问题,更容易通过局部调整来修复。

因此,插入过程可以简化为:解决因插入红色节点而导致的“双红”问题。

这一段其实和AVL树的插入一样。

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}else{Node* parent = nullptr;Node* cur = _root;//找到节点所处的位置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);cur->_col = RED;//连接节点cur->_parent = parent;if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}//通过调整来恢复红黑树性质//...}
}

2.1.1 情况1:叔叔节点U是红色

这是最简单的情况。

处理策略:重新着色

操作步骤:

  1. 将父节点P和叔叔节点U都变为黑色。
  2. 将祖父节点G变为红色。
  3. 把祖父节点G当作新的当前节点C,继续向上检查

为什么这样做?

  • 步骤1解决了P和C之间的“双红”问题。
  • 步骤2保证了以G为根的子树黑色高度不变(因为黑色节点只是从P和U转移到了G)。
  • 步骤3是因为G现在变成了红色,如果G的父节点也是红色,就会产生新的“双红”问题,需要继续向上处理。如果调整后,最顶部的节点是黑色,则不用向上处理,插入结束(接下来的情况二就是这样)。

特殊情况:如果G是根节点,在最后需要将其重新变为黑色(规则2)。

注意:只有是出现双红的情况,祖父节点G一定是黑色,因为P是红色。

在这里插入图片描述

2.1.2 情况2:叔叔节点U是黑色或为空(NIL)

这种情况需要旋转来解决。旋转的目的是为了提升C节点,同时保持二叉搜索树的性质。

情况2又分为两个子情况,取决于当前节点C是父节点P的左孩子还是右孩子。

(1)情况2a:直线型 (LL / RR)
  • LL型:P是G的左孩子,C是P的左孩子。
  • RR型:P是G的右孩子,C是P的右孩子。

处理策略:一次旋转 + 重新着色

操作步骤(以LL型为例):

  1. 对祖父节点G进行一次右旋。
  2. 重新着色:将原来的父节点P变为黑色,原来的祖父节点G变为红色。
  3. 调整结束,无需继续向上(p为黑色)
    在这里插入图片描述

为什么这样做?

  • 右旋使得P成为了新的局部根节点。
  • 着色后,P(黑色)隔开了C(红色)和G(红色),彻底解决了“双红”问题。
  • 旋转和着色后,该子树的黑色高度保持不变。

另附RR型示意图:
在这里插入图片描述

(2)情况2b:折线型 (LR / RL)
  • LR型:P是G的左孩子,C是P的右孩子。
  • RL型:P是G的右孩子,C是P的左孩子。

处理策略:两次旋转 + 重新着色

操作步骤(以LR型为例):

  1. 对父节点P进行一次左旋。这一步将LR型转换成了LL型。
  2. 对祖父节点G进行一次右旋。
  3. 重新着色:将当前节点C变为黑色,原来的祖父节点G变为红色。
  4. 调整结束,无需继续向上(cur为黑色)
    在这里插入图片描述

为什么这样做?

  • 第一次旋转(左旋)将结构标准化。左旋后,其实已经就是情况2a了
  • 第二次旋转(右旋)将C提升到局部根节点的位置。
  • 着色后,C(黑色)成为了新的局部根,隔开了两边的红色节点,彻底解决了问题。

另附RL型示意图:
在这里插入图片描述

2.1.3 代码实现

左右单旋转的代码如下:
左右单旋转的详细讲解,可以看我的关于AVL树的博客:详细请点击<——

private:void RotateL(Node* parent){Node* ppnode = parent->_parent;Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}}void RotateR(Node* parent){Node* ppnode = parent->_parent;Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}}

红黑树插入的代码实现:

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}else{Node* parent = nullptr;Node* cur = _root;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);cur->_col = RED;cur->_parent = parent;if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}while (parent && parent->_col == RED)//满足条件就不断向上调整{Node* grandfather = parent->_parent;if (parent == grandfather->_left)//parent在grandfather的左边的情况{Node* uncle = grandfather->_right;//uncle叔叔节点对应在grandfather右边if (uncle && uncle->_col == RED)//处理uncle存在且颜色为红色的情况{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;_root->_col = BLACK;//保证红黑树的根节点始终为黑色}else//处理uncle不存在,或者uncle存在且颜色为黑色的情况{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else //cur = parent->_right{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else //parent == grandfather->_right//parent在grandfather的右边的情况{Node* uncle = grandfather->_left;//uncle叔叔节点对应在grandfather左边if (uncle && uncle->_col == RED)//处理uncle存在且颜色为红色的情况{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;_root->_col = BLACK;//保证红黑树的根节点始终为黑色}else//处理uncle不存在,或者uncle存在且颜色为黑色的情况{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else //cur = parent->_left{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}return true;}
}

2.2 IsBalance

bool CheckColour(Node* root, int blacksum, int leftpathblack)
{// 基准情况:到达叶子节点if (root == nullptr) {// 检查性质5:所有路径黑色节点数相同if (blacksum != leftpathblack) {cout << "每条路径上的黑色节点的数目不同" << endl;return false;}return true;}Node* parent = root->_parent;// 检查性质4:没有连续的红节点if (parent && parent->_col == RED && root->_col == RED) {cout << root->_kv.first << ':' << "出现两个连续的红节点" << endl;return false;}// 统计当前路径的黑色节点数if (root->_col == BLACK)blacksum++;// 递归检查左右子树return CheckColour(root->_left, blacksum, leftpathblack) &&CheckColour(root->_right, blacksum, leftpathblack);
}bool IsBalance(Node* root)
{if (root == nullptr) return true;// 检查性质2:根节点必须是黑色if (root->_col != BLACK) {cout << "根节点颜色错误不为黑色" << endl;return false;}// 计算最左路径的黑色节点数量作为基准int leftpathblack = 0;Node* cur = root;while (cur) {if (cur->_col == BLACK)leftpathblack++;cur = cur->_left;}return CheckColour(root, 0, leftpathblack);
}bool IsBalance()
{return IsBalance(_root);
}

这段代码是用于检查红黑树是否平衡的函数实现。我来详细解释每个部分的功能:

整体结构

  • IsBalance(): 公开接口,调用内部实现。
  • IsBalance(Node* root): 检查根节点和计算左路径黑色节点数。
  • checkColour(): 递归检查红黑树性质。

函数详解

  1. IsBalance(Node* root)

功能

  • 检查根节点是否为黑色(红黑树性质2)。
  • 计算从根节点到最左叶子节点的黑色节点数量,作为基准值。
  • 调用 checkColour 进行递归检查。
  1. CheckColour(Node* root, int blacksum, int leftpathblack)

功能

  • 性质4检查:确保没有连续的红节点。
  • 性质5检查:确保所有路径的黑色节点数相同。
  • 递归遍历整棵树。

检查的红黑树性质

  1. 性质2:根节点是黑色(在 IsBalance 中检查)。
  2. 性质4:红色节点的子节点必须是黑色(在 CheckColour 中检查)。
  3. 性质5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(在 CheckColour 中检查)。

2.3 Height

int Height()
{return Height(_root);
}int Height(Node* root)
{if (root == nullptr){return 0;}int heightL = Height(root->_left);int heightR = Height(root->_right);return  heightL > heightR ? heightL + 1 : heightR + 1;
}

3. 红黑树与AVL树的对比

选择AVL树的场景:

  • 查找频率远高于插入删除
  • 数据相对有序或接近有序
  • 涉及磁盘I/O等慢速存储
    • I/O成本极高,减少查找次数是首要目标 → 选高度更低的AVL树

选择红黑树的场景:

  • 频繁插入删除操作
  • 数据随机分布
  • 纯内存操作
    • I/O成本可忽略,减少数据移动和缓存失效更重要 → 选旋转更少的红黑树

现实世界的例子:

适合AVL树的场景:

  1. 维基百科全文索引(构建一次,查询海量)
  2. 编译器符号表(构建相对少,查找频繁)
  3. 只读配置数据库

适合红黑树的场景:

  1. Linux进程调度(频繁插入删除进程)
  2. 数据库事务管理(频繁更新)
  3. 实时游戏状态管理

为啥有序的数的插入更适合AVL树?

根本原因:

  1. 调整策略不同
// AVL树:局部调整
if (平衡因子 > 1) {旋转调整();  // 只影响局部子树
}// 红黑树:可能传播调整  
while (父节点是红色 && 违反规则) {颜色变换();if (仍然违反规则) {旋转调整();  // 可能向上传播}
}
  1. 有序数据的特殊性

有序数据插入会形成"链式"结构,这正是:

  • AVL树擅长处理的:通过单次旋转就能恢复平衡
  • 红黑树不擅长的:需要复杂的颜色变换和可能的多次调整

4. 源代码

RBTree.h

#pragma once
#include <iostream>using namespace std;enum Colour
{RED,BLACK
};template<typename K, typename 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<typename K, typename V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:RBTree():_root(nullptr){}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}else{Node* parent = nullptr;Node* cur = _root;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);cur->_col = RED;cur->_parent = parent;if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{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 = cur->_parent;_root->_col = BLACK;}else{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else //cur = parent->_right{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else //parent == grandfather->_right{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;_root->_col = BLACK;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else //cur = parent->_left{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}return true;}}bool CheckColour(Node* root, int blacksum, int leftpathblack){if (root == nullptr){if (blacksum != leftpathblack){cout << "每条路径上的黑色节点的数目不同" << endl;return false;}return true;}Node* parent = root->_parent;if (parent && parent->_col == RED && root->_col == RED){cout << root->_kv.first << ':' << "出现两个连续的红节点" << endl;return false;}if (root->_col == BLACK)blacksum++;return CheckColour(root->_left, blacksum, leftpathblack) &&CheckColour(root->_right, blacksum, leftpathblack);}bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;if (root->_col != BLACK){cout << "根节点颜色错误不为黑色" << endl;return false;}int leftpathbalck = 0;Node* cur = root;while (cur){if (cur->_col == BLACK)leftpathbalck++;cur = cur->_left;}return CheckColour(root, 0,  leftpathbalck);}int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr){return 0;}int heightL = Height(root->_left);int heightR = Height(root->_right);return  heightL > heightR ? heightL + 1 : heightR + 1;}private:void RotateL(Node* parent){_rotatesum++;Node* ppnode = parent->_parent;Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}}void RotateR(Node* parent){_rotatesum++;Node* ppnode = parent->_parent;Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}}private:Node* _root;
public:int _rotatesum = 0;
};
http://www.dtcms.com/a/557025.html

相关文章:

  • 许昌网站制作公司百度指数数据分析平台入口
  • 【笔记】解决 ComfyUI 安装 comfy-mtb 节点后 “Face restoration models not found.” 报错
  • 简洁企业网站模板卖水果网站模板
  • Nginx缓存机制优化:FastCGI缓存与浏览器缓存
  • 襄阳万家灯火网站建设爱用建站平台
  • 3.2.2.SpringMVC简介
  • 帝国cms影视网站模板网站app的区别
  • Rust 结构体方法(Methods):为数据附加行为
  • Android Cursor AI代码编辑器
  • git add 一条命令太长换行
  • 数据仓库与传统数据库开发工具架构差异:Web 架构 vs 客户端工具
  • 百度网站快速排名公司营销策略ppt模板
  • 外骨骼机器人:下肢助力走路,减负 30% 的硬核机械魔法
  • Linux基础I/O-打开新世界的大门:文件描述符的“分身术”与高级重定向
  • 用Python来学微积分25-微积分中的函数奥秘:单调性、极值与最值
  • 免费信息网站排名做动画视频的网站有哪些
  • 从零搭建多子网 DHCP 服务:CentOS 双网卡多作用域实战与原理解析
  • 再议c语言的直接访问和间接访问
  • 从零开始的QT开发指南:(一)背景、特性与环境搭建
  • 网站购物车实现wordpress怎么调用分类的文章
  • oracle 19c搭建dataguard(ADG)全过程
  • 网站集群建设方案兰州免费网站建设
  • 低成本低成本低成本
  • 机器学习核心概念详解(回归、分类和聚类)
  • 基于SpringBoot+Vue的零食仓库管理系统(redis缓存、摄像头、扫描二维码)
  • ant design 做网站wordpress 上传文件名
  • 跨网络互联技术(加密算法)
  • uniapp/flutter中实现苹果IOS 26 毛玻璃效果、跟随滑动放大动画
  • I.MX6U 启动方式详解
  • flutter 生命周期管理:从 Widget 到 State 的完整解析