前言:
学习本章,需要先学习 AVL树的 旋转,因为 红黑树也需要旋转调整来平衡,下面讲解将不赘述 旋转的原理和操作
红黑树的旋转 和 AVL树的旋转 唯一不同的是:旋转的判断使用逻辑
AVL树的旋转 可以通过 平衡因子 判断使用哪一种旋转
红黑树的旋转 则 直接通过 判断 爷爷 grandfather、父亲 parent、自己 cur 三种节点之间的位置关系 来 判断使用哪一种 旋转
(其实原理都一样,只不过AVL树有了平衡因子,可以直接借助平衡因子判断,其核心还是爷父子三者位置关系)
🦖1. 红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是 Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。
(即 最长路径 长度一定 小于等于 最短路径的*2 )
🦖2. 红黑树的 4 条性质(决定代码实现逻辑)
(1)二叉搜索树的结构
(2)根和叶子(NULL)都是黑色
(3)不存在连续的两个红色结点
(4)任一结点到叶所有路径黑结点数量相同
四条性质可以总结成 四条口诀
左根右、根叶黑、不红红、黑路同
🦖⭐(1)左根右
即 左根右 的 二叉搜索树结构
🦖⭐(2)根叶黑
根和叶子(NULL)都是黑色
划蓝色虚线的节点 10即为 根节点
划蓝色虚线的 长方形节点,空节点 NULL
这两种节点都必须是 黑色!
🦖⭐(3)不红红
不存在连续的两个红色结点
下图中 节点 7 和 节点 5 两个连续红节点的情况不合法,需要进一步调整(后序讲解)
🦖⭐(4)黑路同
任一结点到叶所有路径黑结点数量相同
下图中,每条路径的 黑色节点个数 都是 3 ,这就是合法
注意:一条路径的终点一定是 NULL 空节点!!!
🦖3. 为什么说 "红黑树的最长路径不会超过最短路径的两倍 "
因为所有路径黑色节点的数量必须相同(黑路同),同时 红色节点不能连续出现(不红红)
因此 最长路径一定是 一黑一红 的排列
则 最长的那条路径:即使下面再加一个 红色节点,也只是刚好 是最短路径的两倍,而绝对不会超过 最左边的最短路径
🦖4. 红黑树节点 定义
cpp// 设置颜色枚举值 enum Colour { RED, BLACK }; template<class K, class V> struct RBTreeNode { typedef RBTreeNode<K, V> Node; pair<K, V> _data; Node* _left; Node* _right; Node* _parent; Colour _col; RBTreeNode(const T& data) :_data(data) , _left(nullptr) , _right(nullptr) , _parent(nullptr) {} };
🦖5. 红黑树的插入
🦖5.1 插入的节点 默认是 红色
为什么?看下面例子
🦖⭐当 插入的节点 7 为 黑色时
黑色节点 的 插入,必然会违反 "黑路同" 的性质,因此要对其他节点都进行调整(其他节点都要再加一个黑色节点)
⭐当 插入的节点 7 为 红色 ,且 该节点插在 黑色节点 8 下面时
可以发现,插入一个 红色节点并没有影响 任何一个 红黑树的性质,即不用做出调整
⭐当 插入的节点 7 为 红色 ,且 该节点插在 红色节点 8 下面时
若 在 红色节点后面插入一个 红色节点,会违反 "不红红" 的性质,则需要调整
综上所述,插入黑色节点就一定需要调整,插入红色节点却可能不需要调整
因此,插入红色节点的性价比最高
🦖5.2 插入节点后,若性质被破坏,分三种情况调整
(注意:是性质被破坏了,才需要调整,没被破坏不需要调整)
🦖**(1)插入结点是根结点:**
直接将 节点变黑就行
如果非根节点:看叔叔颜色
🦖**(2)插入结点的叔叔 uncle 是红色**:
该情况处理步骤:
1、将叔父爷变色(即除了自己 cur 以外的三个节点)
2、再将 cur 指向 爷爷
(然后继续对这个 cur 进行这 红黑树 4条性质的判定,看是否违反,即 从 cur 开始 继续向上调整)
🦖**(3)插入结点的叔叔 uncle 是黑色**:旋转 + 变色
注意:黑色节点也可以是 NULL 空节点
直接通过 判断 爷爷 grandfather、父亲 parent、自己 cur 三种节点之间的位置关系 来 判断使用哪一种 旋转
因为 单旋 LL型 和 RR 型的原理一致,双旋 LR 型 和 RL 型的原理一致
下面我就以 LL 型 和 LR 型举例讲解旋转的步骤
🦖LL 型:
1、以爷爷为旋转点,向右旋转
2、变色:爷变红,父变黑
🦖LR 型
1、先 以 father 为旋转点 旋转,再以 爷爷 为旋转点 旋转
2、爷变红,cur 变黑
🦖小结:都是固定步骤
单旋 LL型 和 RR 型
旋转:以爷爷为旋转点 左旋 或 右旋(父亲为 旋转中心轴)
变色:爷变红,父变黑
双旋 LR 型 和 RL 型
旋转:先 旋转 父亲 father,再 旋转 爷爷
变色:爷变红,cur 变黑
🦖5.3 插入节点中 旋转变色逻辑 代码讲解
根据上面的讲解,可以发现,决定红黑树 旋转 or 变色 的是 爷父子的位置关系 和 叔叔的颜色
因此代码逻辑 也要 以这两点为中心 设计
🦖伪代码:
因为插入节点是 红色,父亲为空时,违反 "不红红" 的性质,则进入循环执行调整
while ( 父亲不为空 同时 父亲的颜色为红色 )
{
if ( 父亲是 爷爷 的左 孩子 )
if ( 叔叔是 红色 )
else if ( 叔叔是 黑色 )
if ( cur 是 父亲的左 )
else if ( cur 是 父亲的 右)
else if ( 父亲是 爷爷 的 右 孩子 )
if ( 叔叔是红色 )
else if ( 叔叔是 黑色 )
if ( cur 是 父亲的 左)
else if ( cur 是 父亲的 右 )
}
🦖实际代码:
cpp// 变色调整: while (parent && parent->_col == RED) { Node* Grandfather = parent->_parent; /* g p u */ // 父亲是 爷爷 的左孩子 if (parent == Grandfather->_left) { Node* Uncle = Grandfather->_right; // 叔叔是 红色:三人变色,cur指爷 if (Uncle && Uncle->_col == RED) { parent->_col = BLACK; Uncle->_col = BLACK; Grandfather->_col = RED; cur = Grandfather; parent = cur->_parent; } // 叔叔是 黑色:旋转后变色 else if (Uncle == nullptr || Uncle->_col == BLACK) { // 看 cur 的位置:决定单旋 or 双旋 if (cur == parent->_left) { /* 右单旋 + 变色 g p u c */ rotateLL(Grandfather); // 爷变红,父变黑 Grandfather->_col = RED; parent->_col = BLACK; } else if (cur == parent->_right) { /* 双旋(先左旋后右旋) + 变色 g p u c */ rotateRR(parent); // p 先 左旋 rotateLL(Grandfather); // g 再右旋 // 爷变红,cur 变黑 Grandfather->_col = RED; cur->_col = BLACK; } break; // 旋转后,退出循环 } } // 父亲是 爷爷 的右孩子 else if (parent == Grandfather->_right) { Node* Uncle = Grandfather->_left; // 叔叔是 红色:三人变色,cur指爷 if (Uncle && Uncle->_col == RED) { parent->_col = BLACK; Uncle->_col = BLACK; Grandfather->_col = RED; cur = Grandfather; parent = cur->_parent; } // 叔叔是 黑色:旋转后变色 else if (Uncle == nullptr || Uncle->_col == BLACK) { // 看 cur 的位置:决定单旋 or 双旋 if (cur == parent->_right) { /* 左单旋 + 变色 g u p c */ rotateRR(Grandfather); // 爷变红,父变黑 Grandfather->_col = RED; parent->_col = BLACK; } else if (cur == parent->_left) { /* 双旋(先右旋后左旋) + 变色 g u p c */ rotateLL(parent); // p 先 右旋 rotateRR(Grandfather); // g 再左旋 // 爷变红,cur 变黑 Grandfather->_col = RED; cur->_col = BLACK; } break; // 旋转后,退出循环 } } } // 根节点强制变色 _root->_col = BLACK;
🦖5.4 insert 函数 总代码
cpp
// 插入
bool insert(const pair<K, V>& kv) {
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK; // 根节点一定是黑的
return true;
}
Node* cur = _root;
Node* parent = cur;
while (cur) {
if (cur->_kv.first < kv.first) {
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first) {
parent = cur;
cur = cur->_left;
}
else return false;
}
// 在 cur 的位置插入该节点
cur = new Node(kv);
cur->_col = RED; // 新增节点给 红的
// 父连子,子连父
if (parent->_kv.first > kv.first) parent->_left = cur;
else parent->_right = cur;
cur->_parent = parent;
// 变色调整:
while (parent && parent->_col == RED) {
Node* Grandfather = parent->_parent;
/*
g
p u
*/
// 父亲是 爷爷 的左孩子
if (parent == Grandfather->_left) {
Node* Uncle = Grandfather->_right;
// 叔叔是 红色:三人变色,cur指爷
if (Uncle && Uncle->_col == RED) {
parent->_col = BLACK;
Uncle->_col = BLACK;
Grandfather->_col = RED;
cur = Grandfather;
parent = cur->_parent;
}
// 叔叔是 黑色:旋转后变色
else if (Uncle == nullptr || Uncle->_col == BLACK) {
// 看 cur 的位置:决定单旋 or 双旋
if (cur == parent->_left) {
/* 右单旋 + 变色
g
p u
c
*/
rotateLL(Grandfather);
// 爷变红,父变黑
Grandfather->_col = RED;
parent->_col = BLACK;
}
else if (cur == parent->_right) {
/* 双旋(先左旋后右旋) + 变色
g
p u
c
*/
rotateRR(parent); // p 先 左旋
rotateLL(Grandfather); // g 再右旋
// 爷变红,cur 变黑
Grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
// 父亲是 爷爷 的右孩子
else if (parent == Grandfather->_right) {
Node* Uncle = Grandfather->_left;
// 叔叔是 红色:三人变色,cur指爷
if (Uncle && Uncle->_col == RED) {
parent->_col = BLACK;
Uncle->_col = BLACK;
Grandfather->_col = RED;
cur = Grandfather;
parent = cur->_parent;
}
// 叔叔是 黑色:旋转后变色
else if (Uncle == nullptr || Uncle->_col == BLACK) {
// 看 cur 的位置:决定单旋 or 双旋
if (cur == parent->_right) {
/* 左单旋 + 变色
g
u p
c
*/
rotateRR(Grandfather);
// 爷变红,父变黑
Grandfather->_col = RED;
parent->_col = BLACK;
}
else if (cur == parent->_left) {
/* 双旋(先右旋后左旋) + 变色
g
u p
c
*/
rotateLL(parent); // p 先 右旋
rotateRR(Grandfather); // g 再左旋
// 爷变红,cur 变黑
Grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
// 修改一:根节点强制变色
_root->_col = BLACK;
return false;
}
🦖6. 红黑树的删除
红黑树的删除本节不做讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》 http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html
🦖7. 红黑树与AVL树的比较
之前讲到的AVL树,它是左右子树的高度相差不会超过一,可以发现 AVL树 对于平衡的要求会更加严格 ,因此 AVL树在树高上面要比红黑树控制的更加平衡,查询节点的时间复杂度为 logN
因为红黑树的最长路径 可以为最短路径的2倍,因此 查询节点的时间复杂度为 log 2*N
所以在查询上面红黑树要略逊于 AVL树 ,当然在时间复杂度上都是同一个数量级,都是O(logN),差距不会太大
恰恰因为 AVL树 要严格的控制树的平衡,因此 插入删除 操作后,旋转的次数较多
而 红黑树 插入删除 操作中,旋转的次数较少
所以相比之下, AVL树 在查询上边呢更高效;红黑树 在插入删除上边更高效
在实际应用当中呢,红黑树用的更广泛一些,比如说 C++的STL 当中的 map 和 set 都是基于红黑树实现的(下一个章节会讲解 【map 和 set 对红黑叔的封装】)
Java 库 、 linux内核 、其他一些库 都有使用 红黑树
🦖8. ⭐红黑树的完整代码
cpp#pragma once #include<iostream> #include<vector> #include<assert.h> using namespace std; /// // 设置颜色枚举值 enum Colour { RED, BLACK }; template<class K, class V> struct RBTreeNode { typedef RBTreeNode<K, V> Node; pair<K, V> _kv; Node* _left; Node* _right; Node* _parent; Colour _col; RBTreeNode(const pair<K, V>& data) :_kv(data) , _left(nullptr) , _right(nullptr) , _parent(nullptr) {} }; template<class K, class V> class RBTree { public: typedef RBTreeNode<K, V> Node; RBTree() = default; ~RBTree() { destory(_root); _root = nullptr; } // 查找 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; } else { return cur; } } return nullptr; } // 插入 bool insert(const pair<K, V>& kv) { if (_root == nullptr) { _root = new Node(kv); _root->_col = BLACK; // 根节点一定是黑的 return true; } Node* cur = _root; Node* parent = cur; while (cur) { if (cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else return false; } // 在 cur 的位置插入该节点 cur = new Node(kv); cur->_col = RED; // 新增节点给 红的 // 父连子,子连父 if (parent->_kv.first > kv.first) parent->_left = cur; else parent->_right = cur; cur->_parent = parent; // 变色调整: while (parent && parent->_col == RED) { Node* Grandfather = parent->_parent; /* g p u */ // 父亲是 爷爷 的左孩子 if (parent == Grandfather->_left) { Node* Uncle = Grandfather->_right; // 叔叔是 红色:三人变色,cur指爷 if (Uncle && Uncle->_col == RED) { parent->_col = BLACK; Uncle->_col = BLACK; Grandfather->_col = RED; cur = Grandfather; parent = cur->_parent; } // 叔叔是 黑色:旋转后变色 else if (Uncle == nullptr || Uncle->_col == BLACK) { // 看 cur 的位置:决定单旋 or 双旋 if (cur == parent->_left) { /* 右单旋 + 变色 g p u c */ rotateLL(Grandfather); // 爷变红,父变黑 Grandfather->_col = RED; parent->_col = BLACK; } else if (cur == parent->_right) { /* 双旋(先左旋后右旋) + 变色 g p u c */ rotateRR(parent); // p 先 左旋 rotateLL(Grandfather); // g 再右旋 // 爷变红,cur 变黑 Grandfather->_col = RED; cur->_col = BLACK; } break; } } // 父亲是 爷爷 的右孩子 else if (parent == Grandfather->_right) { Node* Uncle = Grandfather->_left; // 叔叔是 红色:三人变色,cur指爷 if (Uncle && Uncle->_col == RED) { parent->_col = BLACK; Uncle->_col = BLACK; Grandfather->_col = RED; cur = Grandfather; parent = cur->_parent; } // 叔叔是 黑色:旋转后变色 else if (Uncle == nullptr || Uncle->_col == BLACK) { // 看 cur 的位置:决定单旋 or 双旋 if (cur == parent->_right) { /* 左单旋 + 变色 g u p c */ rotateRR(Grandfather); // 爷变红,父变黑 Grandfather->_col = RED; parent->_col = BLACK; } else if (cur == parent->_left) { /* 双旋(先右旋后左旋) + 变色 g u p c */ rotateLL(parent); // p 先 右旋 rotateRR(Grandfather); // g 再左旋 // 爷变红,cur 变黑 Grandfather->_col = RED; cur->_col = BLACK; } break; } } } // 修改一:根节点强制变色 _root->_col = BLACK; return false; } // RR型:左单旋 void rotateRR(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; Node* parentParent = parent->_parent; // 1、subRL变成parent的右孩子 parent->_right = subRL; // subRL 是有可能为 空的 if (subRL) { subRL->_parent = parent; } // 2、parent变成subR的左孩子 subR->_left = parent; parent->_parent = subR; // 3、subR变成当前子树的根 // parentParent 是指 刚开始的 parent 的父亲:若 parent 是 _root 则 parentParent 为空,否则不为空,则该树就是子树 if (parentParent) { if (parent == parentParent->_right) parentParent->_right = subR; else parentParent->_left = subR; subR->_parent = parentParent; } // 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空 else { _root = subR; subR->_parent = nullptr; } } // LL型:右单旋 void rotateLL(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; Node* parentParent = parent->_parent; // 1、subLR变成parent的左孩子 parent->_left = subLR; // subRL 是有可能为 空的 if (subLR) { subLR->_parent = parent; } // 2、parent变成subL的右孩子 subL->_right = parent; parent->_parent = subL; // 3、subL 变成当前子树的根 // parentParent 是指 刚开始的 parent 的父亲:若 parent 是 _root 则 parentParent 为空,否则不为空,则该树就是子树 if (parentParent) { if (parent == parentParent->_right) parentParent->_right = subL; else parentParent->_left = subL; subL->_parent = parentParent; } // 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空 else { _root = subL; subL->_parent = nullptr; } } // LR 型:subL 先 左旋, parent 右旋 void rotateLR(Node* parent) { rotateRR(parent->_left); rotateLL(parent); } // RL 型:subR 先 右旋, parent 左旋 void rotateRL(Node* parent) { rotateLL(parent->_right); rotateRR(parent); } // 中序遍历 void InOrder() { _InOrder(_root); cout << '\n'; } // 获取该树的高度 int Height() { return _Height(_root); } // 获取节点个数 int Size() { return _Size(_root); } // 判断是否是 红黑树 bool IsValidRBTree() { if (_root == nullptr) return false; else if (_root && _root->_col == RED) return false; // 遍历一条路,记录一条路上一共固定有多少个黑色节点 int cnt = 0; Node* cur = _root; while (cur) { if (cur->_col == BLACK) cnt++; cur = cur->_left; } return _IsValidRBTree(_root, 0, cnt); } private: // 判断是否是 红黑树 bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount) { // 1、看根节点是否是 黑的 // 2、看每条路径的 黑色节点数量是否相同 // 3、检查是否有连续的红节点:遇到一个红节点就判断其父亲是否是 红的 //走到null之后,判断 k 和 blackCount 是否相等:即一条路径上的 黑色节点数量是否为固定值 if (pRoot == nullptr) { if (k != blackCount) { cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl; return false; } return true; } // 统计黑色节点的个数 if (pRoot->_col == BLACK) k++; // 检测当前节点与其双亲是否都为红色 Node* pParent = pRoot->_parent; if (pParent && pParent->_col == RED && pRoot->_col == RED) { cout << "违反性质三:没有连在一起的红色节点" << endl; return false; } return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount); } int _Size(Node* pRoot) { if (pRoot == nullptr) return 0; //if (pRoot->_left == nullptr && pRoot->_right == nullptr) return 1; return 1 + _Size(pRoot->_left) + _Size(pRoot->_right); } int _Height(Node* pRoot) { if (pRoot == nullptr) return 0; return 1 + max(_Height(pRoot->_left), _Height(pRoot->_right)); } // 销毁一棵树:后序遍历 void destory(Node* root) { if (root == nullptr) { return; } destory(root->_left); destory(root->_right); delete root; } void _InOrder(const Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << (root->_kv).first << " : " << (root->_kv).second << '\n'; _InOrder(root->_right); } Node* _root = nullptr; };
参考文献和资料
B站 up :蓝不过海呀
【红黑树 - 定义, 插入, 构建】https://www.bilibili.com/video/BV1Xm421x7Lg?vd_source=bea8fdb0eb9c0c7d500ffd191a292977