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

【揭秘红黑树:高效数据结构解析】

红黑树

  • 前言
  • 一、红黑树的概念
    • (一)红黑树的规则
    • (二)红黑树最长路径与最短路径
    • (三)红黑树的效率
  • 二、红黑树的实现
    • (一) 红黑树的结构
    • (二) 红黑树的插入
      • 1.情况一
      • 2.情况二
      • 3.情况三
      • 4.插入代码示例
    • (三)红黑树的验证
  • 三、红黑树整体代码示例
  • 总结


前言

`
红黑树是在二叉搜索树的基础上多出一些变化规则,以此让左右子树趋近平衡。

注意:
红黑树是趋近平衡,AVL树才是平衡;
AVL和红黑树不论底层实现还是功能都基本上一致。

一、红黑树的概念

红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。

(一)红黑树的规则

1. 每个结点不是红⾊就是⿊⾊   
2. 根结点是⿊⾊的    
3. 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊结点。   
4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点 

说明:《算法导论》等书籍上补充了⼀条每个叶⼦结点(NIL)都是⿊⾊的规则。他这⾥所指的叶⼦结点不是传统的意义上的叶⼦结点,⽽是我们说的空结点,有些书籍上也把NIL叫做外部结点。NIL是为了⽅便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们知道⼀下这个概念即可。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(二)红黑树最长路径与最短路径

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

(三)红黑树的效率

假设N是红⿊树树中结点数量,h最短路径的⻓度,那么2^ h − 1 <= N < 2 ^(2∗h) − 1 , 由此推出 h ≈ logN ,也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径 2 ∗ logN ,那么时间复杂度还是 O(logN).
红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格。
在这里插入图片描述

二、红黑树的实现

红黑树的主要接口有插入、删除、查找、遍历。
其中,红黑树的删除、查找、遍历代码实现几乎和二叉树搜索树一致,故不再重复说明,可以参考[二叉搜索树]。(https://blog.csdn.net/2501_90956005/article/details/150230291?spm=1001.2014.3001.5501)

而红黑树的实现中最复杂的就是红黑树的插入,重点理解!

(一) 红黑树的结构

/ 枚举值表⽰颜⾊
enum Colour
{RED,BLACK
};
// 这⾥我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{// 这⾥更新控制平衡也要加⼊parent指针pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col=RED;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public://红黑树的常用接口实现//......private:Node* _root = nullptr;
};

(二) 红黑树的插入

首先,红黑树的插入要分为以下进行讨论:
说明:下图中假设我们把新增结点标识为c (cur),c的⽗亲节点标识为p(parent),p的⽗亲节点标识为g(grandfather),p的兄弟节点标识为u(uncle)
情况一:u存在且为红
情况二:u不存在或者是⿊⾊的,且插入节点靠近树的边缘,如图一。
情况三:u不存在或者是⿊⾊的,且插入节点靠近树的内部,如图二。

图一:
在这里插入图片描述

图二:
在这里插入图片描述

1.情况一

u存在且为红

这是三种情况中最简单的情况。
处理:c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红。在把g当做新的c,继续往上更新。
图示:
在这里插入图片描述

2.情况二

u不存在或者是⿊⾊的,且插入节点靠近树的边缘

这种情况我们仍然像情况一样进行处理,就会破坏红黑树的结构规则,所以这里需要像AVL树一样进行旋转处理。

旋转分为向左旋、向右旋转。
下面是我画的抽象图:
其中,g为旋转函数传入参数,圈起来的内容表示关系无变化,连线内容表示关系出现变化。
在这里插入图片描述

代码示例:
注:旋转结束后要更新节点颜色。

//向右旋转
void Rotate_R(Node*& grandparent)
{Node* parent = grandparent->_left;Node* temp = parent->_right;Node* pgrandpaent = grandparent->_parent;grandparent->_left = temp;if (temp)temp->_parent = grandparent;parent->_right = grandparent;grandparent->_parent = parent;if (grandparent == _root){_root = parent;parent->_parent = nullptr;}else{if (pgrandpaent->_left = grandparent){pgrandpaent->_left = parent;parent->_parent = pgrandpaent;}else{pgrandpaent->_right= parent;parent->_parent = pgrandpaent;}}
}
//向左旋转
void Rotate_L(Node*& grandparent)
{Node* parent = grandparent;Node* temp = parent->_left;Node* pgrandparent = grandparent->_parent;grandparent->_right = temp;if (temp)temp->_parent = grandparent;grandparent->_parent = parent;parent->_left = grandparent;if (grandparent == _root){_root = parent;parent->_parent = nullptr;}else{if (pgrandparent->_left = grandparent){pgrandparent->_left = parent;parent->_parent = pgrandparent;}else{pgrandparent->_right = parent;parent->_parent = pgrandparent;}}
}

3.情况三

u不存在或者是⿊⾊的,且插入节点靠近树的内部

这中情况需要复用两次单次旋转来进行处理,且两次旋转必须为先左旋后右旋或者先右旋后左旋

然后再对节点颜色进行更新。

注:通过观察旋转前后变化图可以知道,节点颜色的更新可以只看结果,而不去注意过程。

4.插入代码示例

void Rotate_R(Node*& grandparent)
{Node* parent = grandparent->_left;Node* temp = parent->_right;Node* pgrandpaent = grandparent->_parent;grandparent->_left = temp;if (temp)temp->_parent = grandparent;parent->_right = grandparent;grandparent->_parent = parent;if (grandparent == _root){_root = parent;parent->_parent = nullptr;}else{if (pgrandpaent->_left = grandparent){pgrandpaent->_left = parent;parent->_parent = pgrandpaent;}else{pgrandpaent->_right= parent;parent->_parent = pgrandpaent;}}
}void Rotate_L(Node*& grandparent)
{Node* parent = grandparent;Node* temp = parent->_left;Node* pgrandparent = grandparent->_parent;grandparent->_right = temp;if (temp)temp->_parent = grandparent;grandparent->_parent = parent;parent->_left = grandparent;if (grandparent == _root){_root = parent;parent->_parent = nullptr;}else{if (pgrandparent->_left = grandparent){pgrandparent->_left = parent;parent->_parent = pgrandparent;}else{pgrandparent->_right = parent;parent->_parent = pgrandparent;}}}bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv.first,kv.second);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > kv.first){parent = cur;cur = cur->_left;}else if (cur->_key < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv.first, kv.second);if (parent->_key>kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while(parent&&parent->_col==RED){	Node* grandparent = parent->_parent;if (grandparent->_left == parent){Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}else{if (parent->_left == cur){Rotate_R(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{Rotate_L(grandparent);Rotate_R(grandparent);grandparent->_col = RED;cur->_col = BLACK;}return true;}}else{Node* uncle = grandparent->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}else{if (parent->_left == cur){Rotate_L(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{Rotate_R(grandparent);Rotate_L(grandparent);grandparent->_col = RED;cur->_col = BLACK;}break;}}}return true;
}

(三)红黑树的验证

在进行完红黑树的增删查操作后,我们可能会对红黑树的结果重新验证,判断它还是不是红黑树。
这里仅提供思路:
使用前序遍历,期间如果遍历到红色节点,对该节点的父节点颜色进行判断,如果父节点也为红色,那么他就不是红黑树,返回false;反之继续遍历,直到遍历结束返回true。

三、红黑树整体代码示例

#pragma once#include<iostream>
#include<string>using namespace std;enum Colour
{RED,BLACK
};template<typename K,typename V>
struct RBNode
{RBNode(const K& key,const V& value):_key(key),_value(value){}K _key;V _value;RBNode<K, V>* _parent=nullptr;RBNode<K, V>* _left=nullptr;RBNode<K, V>* _right=nullptr;Colour _col=RED;
};template<typename K,typename V>
class RBTree
{typedef RBNode<K, V> Node;
public:void Rotate_R(Node*& grandparent){Node* parent = grandparent->_left;Node* temp = parent->_right;Node* pgrandpaent = grandparent->_parent;grandparent->_left = temp;if (temp)temp->_parent = grandparent;parent->_right = grandparent;grandparent->_parent = parent;if (grandparent == _root){_root = parent;parent->_parent = nullptr;}else{if (pgrandpaent->_left = grandparent){pgrandpaent->_left = parent;parent->_parent = pgrandpaent;}else{pgrandpaent->_right= parent;parent->_parent = pgrandpaent;}}}void Rotate_L(Node*& grandparent){Node* parent = grandparent;Node* temp = parent->_left;Node* pgrandparent = grandparent->_parent;grandparent->_right = temp;if (temp)temp->_parent = grandparent;grandparent->_parent = parent;parent->_left = grandparent;if (grandparent == _root){_root = parent;parent->_parent = nullptr;}else{if (pgrandparent->_left = grandparent){pgrandparent->_left = parent;parent->_parent = pgrandparent;}else{pgrandparent->_right = parent;parent->_parent = pgrandparent;}}}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv.first,kv.second);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > kv.first){parent = cur;cur = cur->_left;}else if (cur->_key < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv.first, kv.second);if (parent->_key>kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while(parent&&parent->_col==RED){	Node* grandparent = parent->_parent;if (grandparent->_left == parent){Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}else{if (parent->_left == cur){Rotate_R(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{Rotate_L(grandparent);Rotate_R(grandparent);grandparent->_col = RED;cur->_col = BLACK;}return true;}}else{Node* uncle = grandparent->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}else{if (parent->_left == cur){Rotate_L(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{Rotate_R(grandparent);Rotate_L(grandparent);grandparent->_col = RED;cur->_col = BLACK;}break;}}}return true;}void InOrder(){_InOrder(_root);}Node* Find(const K& key){if (_root == nullptr)return nullptr;Node* cur = _root;while (cur){if (key>cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " " << root->_value << endl;_InOrder(root->_right);}Node* _root=nullptr;
};

总结

红黑树的插入实现是红黑树学习的核心内容,对于插入的代码最好能自行实现一下,且实现过程中最好多画画图。
同时,红黑树也是map/set封装的底层实现,理解红黑树对于学习map/set打下基础。

http://www.dtcms.com/a/333191.html

相关文章:

  • I/O多路复用特性与实现
  • 全球鲜花速递市场:规模扩张、竞争格局与未来趋势解析
  • Python正则表达式处理Unicode字符完全指南:从基础到高级实战
  • Comfyui进入python虚拟环境
  • LangChain 与 LangGraph:如何选择合适的工具
  • RK3588 recovery模式和misc分区介绍
  • Ant-Design AUpload如何显示缩略图;自定义哪些类型的数据可以使用img预览
  • HTTP 请求方法:GET 与 POST
  • 应用层协议——HTTP
  • Jenkins+Python自动化持续集成详细教程
  • 神经网络设计中关于BN归一化(Normalization)的讨论
  • 扣子(Coze),开源了!Dify 天塌了
  • 淡季磨剑,旺季出鞘!外贸管理软件让淡季备货与旺季冲刺无缝衔接
  • Mini MAX AI应用矩阵测评报告——基于旗下多款产品的综合体验与行业价值分析
  • 亚马逊“十省联动”4.0:产业带跨境转型的全维度赋能路径
  • Linux操作系统--多线程(锁、线程同步)
  • 优秘企业智脑 AISEO 技术拆解:从算法逻辑到 GEO 优化,如何重构企业智能营销底层能力?
  • JVM执行引擎深入理解
  • Redis核心架构
  • Java学习第一百三十六部分——finally块执行时机
  • 如何回答研究过MQ的源码吗
  • Jenkins 环境部署
  • 在多语言大模型中保留文化细微差别:超越翻译
  • IPv4地址和IPv6地址:解析两代互联网协议的本质差异
  • 【swift开发】SwiftUI概述 SwiftUI 全面解析:苹果生态的声明式 UI 革命
  • DevEco Studio 6.0.0 元服务页面跳转失败
  • 机器翻译:模型微调(Fine-tuning)与调优详解
  • leetcode 1780. 判断一个数字是否可以表示成三的幂的和 中等
  • 新手入门Makefile:FPGA项目实战教程(二)
  • 达梦数据库使用控制台disql执行脚本