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

『 C++ 入门到放弃 』- 红黑树

1. 红黑树的概念

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

红黑树有以下几条规则

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

Lightbox

2. 红黑树的实现

2.1 基本结构

// 枚举 => 用来表示一个节点是红还是黑
enum Color{RED,BLACK
};
// Node的结构 ( 以key/value 做演示 )
template<class K,class V>struct RBTNode{RBTNode(const K& key,const V& value):_left(nullptr),_right(nullptr),_parent(nullptr),_key(key),_value(value),_color(RED){}RBTNode<K,V>* _left;RBTNode<K,V>* _right;RBTNode<K,V>* _parent;K _key;V _value;Color _color;};
// 树的结构
template <class K, class V> class RBT {typedef RBTNode<K, V> Node;private:Node *_root = nullptr;
};

2.2 插入

红黑树的插入也需要针对不同情况作处理 => 插入一个新节点后不能破坏红黑树的性质

  1. 如果是空树插⼊,新增结点是⿊⾊结点;如果是⾮空树插⼊,新增结点必须红⾊结点,因为⾮空树插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的。

  2. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束

  3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3 => 此时需要根据不同情况作处理

(1) 变色、不旋转 => 连续红节点且叔叔节点为红节点

旋转的特点为叔叔节点是黑节点

(2) 变色、单旋

img

img

//右旋逻辑 => 和AVL树是一样的,只是不用调整平衡因子
void RotateR(Node *parent) {
/*右旋的目标:1. 将parent的左子节点subL提升为新的根节点2. 将subL的右子节点subLR接到parent的左子节点位置3. 将parent接到subL的右子节点位置4. 更新所有相关的父节点指针,维护树的完整性*/Node *subL = parent->_left; // 获取左子节点(将成为新的根节点)Node *subLR = subL->_right; // 获取左子节点的右子节点(需要重新接)// 步骤1:将subLR接到parent的左子节点位置parent->_left = subLR;if (subLR) {subLR->_parent = parent; // 更新subLR的父节点指针}// 步骤2:将parent接到subL的右子节点位置subL->_right = parent;// 步骤3:更新祖父节点的指向关系Node *parentParent = parent->_parent; // 获取祖父节点parent->_parent = subL;               // 更新parent的父节点指针if (parentParent == nullptr) {// 情况A:parent是根节点,subL成为新的根节点_root = subL;subL->_parent = nullptr; // 根节点没有父节点} else {// 情况B:parent不是根节点,需要更新祖父节点的指向if (parent == parentParent->_left) {parentParent->_left = subL; // parent原本是左子节点,subL也成为左子节点} else {parentParent->_right = subL; // parent原本是右子节点,subL也成为右子节点}subL->_parent = parentParent; // 更新subL的父节点指针}
}
// 左旋逻辑
void RotateL(Node *parent) {/*左旋的目标:1. 将parent的右子节点subR提升为新的根节点2. 将subR的左子节点subRL接到parent的右子节点位置3. 将parent接到subR的左子节点位置4. 更新所有相关的父节点指针,维护树的完整性*/Node *subR = parent->_right; // 获取右子节点(将成为新的根节点)Node *subRL = subR->_left;   // 获取右子节点的左子节点(需要重新接)// 步骤1:将subRL接到parent的右子节点位置parent->_right = subRL;if (subRL) {subRL->_parent = parent; // 更新subRL的父节点指针}// 步骤2:将parent接到subR的左子节点位置subR->_left = parent;// 步骤3:更新祖父节点的指向关系Node *parentParent = parent->_parent; // 获取祖父节点parent->_parent = subR;               // 更新parent的父节点指针if (parentParent == nullptr) {// 情况A:parent是根节点,subR成为新的根节点_root = subR;subR->_parent = nullptr; // 根节点没有父节点} else {// 情况B:parent不是根节点,需要更新祖父节点的指向if (parent == parentParent->_right) {parentParent->_right = subR; // parent原本是右子节点,旋转后subR成为新的右子节点} else {parentParent->_left = subR; // parent原本是左子节点,旋转后subR成为新的左子节点}subR->_parent = parentParent; // 更新subR的父节点指针}
}

(3) 双旋、变色

灯箱

img

bool insert(const pair<K, V> &kv) {// 1. 如果根节点为空,直接插入为根节点,并设为黑色if (_root == nullptr) {_root = new Node(kv);_root->_color = BLACK;return true;}// 2. 根节点不为空,寻找插入位置Node *parent = nullptr;Node *cur = _root;while (cur) {// 如果当前节点的key小于插入key,往右子树找if (cur->_kv.first < kv.first) {parent = cur;cur = cur->_right;} else if (cur->_kv.first >kv.first) { // 如果当前节点的key大于插入key,往左子树找parent = cur;cur = cur->_left;} else { // 如果已经存在相同key,不插入return false;}}// 2.2 插入新节点到找到的位置cur = new Node(kv);if (parent->_kv.first < kv.first) {parent->_right = cur;} else {parent->_left = cur;}cur->_parent = parent;// 2.3 插入后进行红黑树性质调整while (parent && parent->_color == RED) {Node *grandfather = parent->_parent; // 祖父节点// 情况1:父节点是祖父的左孩子if (grandfather->_left == parent) {Node *uncle = grandfather->_right; // 叔叔节点// 1.1 叔叔存在且为红色,进行颜色翻转if (uncle && uncle->_color == RED) {parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;// 继续向上调整cur = grandfather;parent = cur->_parent;}// 1.2 叔叔不存在或为黑色,需要旋转else {// 当前节点是父节点的左孩子,右旋祖父if (cur == parent->_left) {RotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;} else { // 当前节点是父节点的右孩子,先左旋父节点再右旋祖父RotateL(parent);RotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;}break; // 调整完毕,跳出循环}// 情况2:叔叔不存在或为黑} else {Node *uncle = grandfather->_left; // 叔叔节点// 2.1 叔叔存在且为红if (uncle && uncle->_color == RED) {parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;// 继续向上调整cur = grandfather;parent = cur->_parent;}// 2.2 叔叔不存在或为黑else {// 当前节点是父节点的右孩子,左旋祖父if (cur == parent->_right) {RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;} else { // 当前节点是父节点的左孩子,先右旋父节点再左旋祖父RotateR(parent);RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}break; // 调整完毕,跳出循环}}}// 最后确保根节点为黑色_root->_color = BLACK;return true;
}

2.3 查找

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;
}

2.4 判断红黑树是否平衡

// 检查红黑树是否满足所有性质// root: 当前检查的节点// blackNum: 当前路径经过的黑色节点数// refNum: 标准黑色节点数(根到最左叶子的黑色节点数)// 返回true表示满足红黑树性质bool Check(Node *root, int blackNum, const int refNum) {if (root == nullptr) {// 遍历到空节点,检查黑色节点数是否一致if (refNum != blackNum) {cout << "存在黑色结点的数量不相等的路径" << endl;return false;}return true;}// 检查是否有连续红色节点if (root->_color == RED && root->_parent->_color == RED) {cout << root->_kv.first << "存在连续的红色结点" << endl;return false;}if (root->_color == BLACK) {blackNum++;}return Check(root->_left, blackNum, refNum) &&Check(root->_right, blackNum, refNum);}// 判断红黑树是否平衡(即是否满足红黑树所有性质)bool IsBalance() {if (_root == nullptr) {return true;}if (_root->_color == RED) {cout << "根节点是红色" << endl;return false;}int refNum = 0;Node *left = _root;// 统计根到最左叶子的黑色节点数while (left) {if (left->_color == BLACK) {refNum++;}left = left->_left;}return Check(_root, 0, refNum);}

3. AVL树 vs. 红黑树

与红黑树相比,AVL 树更加平衡,但在插入和删除操作中可能需要更多旋转。因此,如果应用程序涉及频繁的插入和删除操作,则应优先选择红黑树。如果插入和删除操作较少,而搜索操作较为频繁,则应优先选择AVL 树而非红黑树。

此外,AVL树性质被破坏时一定会旋转,而红黑树不一定 ( 可能只是变色 )

4. set 和 map 的实现

set 和 map 的实现底层是用红黑树实现的,因此在这里也模拟实现

再模拟实现 set 和 map 前要先修改一下我们前面写的红黑树

enum Color { RED, BLACK };// 红黑树节点结构体模板
// T:节点存储的数据类型(如 pair<K, V> 或自定义 struct)
// 设计原因:模板化节点类型,支持多种数据结构
template <class T> struct RBTNode {// 构造函数,初始化数据并默认颜色为红色RBTNode(const T &data): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data),_color(RED) {}RBTNode<T> *_left;   RBTNode<T> *_right;  RBTNode<T> *_parent; T _data;  // 节点存储的数据类型    Color _color;       };// 红黑树迭代器模板
// T:节点数据类型,Ref/Ptr:引用和指针类型
template <class T, class Ref, class Ptr> struct RBTreeIterator {typedef RBTNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node *_node; // 当前迭代器指向的节点Node *_root; // 整棵树的根节点(用于--到end时定位最大节点)RBTreeIterator(Node *node, Node *root) : _node(node), _root(root) {}Ref operator*() { return _node->_data; }   // 解引用,返回节点数据Ptr operator->() { return &_node->_data; } // 指针访问// 前置++,移动到下一个节点(中序遍历)Self &operator++() {if (_node->_right) {Node *leftmost = _node->_right;while (leftmost->_left)leftmost = leftmost->_left;_node = leftmost;} else {Node *cur = _node, *parent = cur->_parent;while (parent && cur == parent->_right) {cur = parent;parent = parent->_parent;}_node = parent;}return *this;}// 前置--,移动到上一个节点(中序遍历)Self &operator--() {if (_node == nullptr) { // 如果为空 => 找最右边的节点Node *rightmost = _root;while (rightmost->_right)rightmost = rightmost->_right;_node = rightmost;// 如果左子树存在 => 找左子树的最右的节点} else if (_node->_left) {Node *rightmost = _node->_left;while (rightmost->_right)rightmost = rightmost->_right;_node = rightmost;} else {Node *cur = _node, *parent = cur->_parent;while (parent && cur == parent->_left) {cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const Self &s) const { return _node != s._node; }bool operator==(const Self &s) const { return _node == s._node; }};// 红黑树主类模板
// K:键类型,T:节点数据类型,KeyOfT:仿函数,从T中提取K
// 设计原因:支持泛型键值类型,KeyOfT适配不同数据结构
template <class K, class T, class KeyOfT> class RBT {typedef RBTNode<T> Node;public:typedef RBTreeIterator<T, T &, T *> Iterator;typedef RBTreeIterator<T, const T &, const T *> ConstIterator;RBT() = default;// 返回最左节点的迭代器(begin)Iterator begin() {Node *leftmost = _root;while (leftmost && leftmost->_left)leftmost = leftmost->_left;return Iterator(leftmost, _root);}// 返回end迭代器(空节点)Iterator end() { return Iterator(nullptr, _root); }ConstIterator begin() const {Node *leftmost = _root;while (leftmost && leftmost->_left)leftmost = leftmost->_left;return ConstIterator(leftmost, _root);}ConstIterator end() const { return ConstIterator(nullptr, _root); }// 析构函数,递归释放所有节点~RBT() {Destroy(_root);_root = nullptr;}// 插入数据,返回插入位置和是否插入成功// 设计思路:先查找插入位置,若已存在则返回已存在节点,否则插入并调整红黑树pair<Iterator, bool> insert(const T &data) {if (_root == nullptr) {_root = new Node(data);_root->_color = BLACK;return make_pair(Iterator(_root, _root), true);}KeyOfT kot;Node *parent = nullptr, *cur = _root;while (cur) {if (kot(cur->_data) < kot(data)) {parent = cur;cur = cur->_right;} else if (kot(cur->_data) > kot(data)) {parent = cur;cur = cur->_left;} else {// 已存在return make_pair(Iterator(cur, _root), false);}}cur = new Node(data);if (kot(parent->_data) < kot(data))parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;// 插入后调整红黑树性质while (parent && parent->_color == RED) {Node *grandfather = parent->_parent;if (grandfather->_left == parent) {Node *uncle = grandfather->_right;if (uncle && uncle->_color == RED) {parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;} else {if (cur == parent->_left) {RotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;} else {RotateL(parent);RotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;}break;}} else {Node *uncle = grandfather->_left;if (uncle && uncle->_color == RED) {parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;} else {if (cur == parent->_right) {RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;} else {RotateR(parent);RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}break;}}}_root->_color = BLACK;return make_pair(Iterator(cur, _root), true);}// 查找数据,返回迭代器Iterator find(const T &data) {KeyOfT kot;Node *cur = _root;while (cur) {if (kot(cur->_data) < kot(data))cur = cur->_right;else if (kot(cur->_data) > kot(data))cur = cur->_left;elsereturn Iterator(cur, _root);}return end();}// 检查红黑树是否满足所有性质bool Check(Node *root, int blackNum, const int refNum) {if (root == nullptr) {if (refNum != blackNum) {cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_color == RED && root->_parent && root->_parent->_color == RED) {cout << "存在连续的红色节点" << endl;return false;}if (root->_color == BLACK)blackNum++;return Check(root->_left, blackNum, refNum) &&Check(root->_right, blackNum, refNum);}// 判断红黑树是否平衡bool IsBalance() {if (_root == nullptr)return true;if (_root->_color == RED) {cout << "根节点是红色" << endl;return false;}int refNum = 0;Node *left = _root;while (left) {if (left->_color == BLACK)refNum++;left = left->_left;}return Check(_root, 0, refNum);}private:// 递归销毁所有节点void Destroy(Node *root) {if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}Node *_root = nullptr; // 红黑树根节点// 右旋操作,维护红黑树平衡void RotateR(Node *parent) {Node *subL = parent->_left;Node *subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node *parentParent = parent->_parent;parent->_parent = subL;if (parentParent == nullptr) {_root = subL;subL->_parent = nullptr;} else {if (parent == parentParent->_left)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}}// 左旋操作,维护红黑树平衡void RotateL(Node *parent) {Node *subR = parent->_right;Node *subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node *parentParent = parent->_parent;parent->_parent = subR;if (parentParent == nullptr) {_root = subR;subR->_parent = nullptr;} else {if (parent == parentParent->_right)parentParent->_right = subR;elseparentParent->_left = subR;subR->_parent = parentParent;}}
};
// myset.h
// 要include 红黑树的代码,再复用
template <class K> class set {// 仿函数,直接返回key本身// 设计原因:set的元素就是key,不需要从pair中提取dstruct SetKeyOfT {const K &operator()(const K &key) { return key; }};public:// 迭代器类型定义,直接复用RBT的迭代器typedef typename RBT<K, K, SetKeyOfT>::Iterator iterator;typedef typename RBT<K, K, SetKeyOfT>::ConstIterator const_iterator;// begin/end接口,支持范围for遍历iterator begin() { return _t.begin(); }iterator end() { return _t.end(); }const_iterator begin() const { return _t.begin(); }const_iterator end() const { return _t.end(); }// 插入元素,返回插入结果pair<iterator, bool> insert(const K &key) { return _t.insert(key); }// 查找元素,返回迭代器iterator find(const K &key) { return _t.find(key); }private:RBT<K, K, SetKeyOfT> _t; // 底层用红黑树实现
};
// mymap.h// map模板类,K为key类型,V为value类型
template <class K, class V> class map {// 仿函数,从pair<K, V>中提取keystruct MapKeyOfT {const K &operator()(const pair<K, V> &kv) { return kv.first; }};public:// 迭代器类型定义,复用RBT的迭代器typedef typename RBT<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBT<K, pair<const K, V>, MapKeyOfT>::ConstIteratorconst_iterator;iterator begin() { return _t.begin(); }iterator end() { return _t.end(); }const_iterator begin() const { return _t.begin(); }const_iterator end() const { return _t.end(); }// 下标操作符,若key不存在则插入默认值V &operator[](const K &key) {pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}// 插入键值对pair<iterator, bool> insert(const pair<K, V> &kv) { return _t.insert(kv); }// 查找key,返回迭代器iterator find(const K &key) { return _t.find(key); }private:RBT<K, pair<K, V>, MapKeyOfT> _t; // 底层用红黑树实现
};
http://www.dtcms.com/a/292619.html

相关文章:

  • 一文详解Java类中的构造器是什么及主要特性
  • 70.爬楼梯
  • ABP VNext 报表:EPPlus DinkToPdf 多格式导出
  • redis秒杀之lua脚本
  • 20250722解决在Ubuntu 24.04.2下编译RD-RK3588开发板的Android13出现找不到python2的问题
  • GraphRAG的部署和生成检索过程体验
  • C++11--锁分析
  • npm全局安装后,依然不是内部或外部命令,也不是可运行的程序或批处理文件
  • 大数据量查询计算引发数据库CPU告警问题复盘
  • 使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第二十二讲)
  • Linux_Ext系列文件系统基本认识(一)
  • Product Hunt 每日热榜 | 2025-07-22
  • “鱼书”深度学习入门 笔记(1)前四章内容
  • day19 链表
  • 【科研绘图系列】R语言绘制柱状堆积图
  • 基于 Vue,SPringBoot开发的新能源充电桩的系统
  • 豪鹏科技锚定 “AI + 固态” 赛道:从电池制造商到核心能源方案引领者的战略跃迁
  • mybatis拦截器实现唯一索引的动态配置
  • 网络基础DAY16-MSTP-VRRP
  • git reset --soft和 git reset --mixed的主要区别
  • 智能制造——解读制造业企业数字化转型实施指南2025【附全文阅读】
  • libgmp库(GNU高精度算术库)介绍
  • 算法训练营day28 贪心算法②122.买卖股票的最佳时机II、55. 跳跃游戏、 45.跳跃游戏II 、1005.K次取反后最大化的数组和
  • Web服务器(Tomcat、项目部署)
  • 0722 数据结构顺序表
  • 循环神经网络--NLP基础
  • <另一种思维:语言模型如何展现人类的时间认知>总结
  • 大型语言模型(Large Language Models,LLM)
  • Science Robotics 机器人成功自主完成猪胆囊切除手术
  • vue3 动态判断 el-table列 用 v-if 是否显示