目录
一、红黑树的概念和性质
二、红黑树的两个结论
三、红黑树节点的定义
四、红黑树的插入
五、红黑树的实现
5.1 - RBT.h
5.2 - test.cpp
六、红黑树和 AVL 树的比较
红黑树(red-black tree)是这样的一棵二叉搜索树:树中的每个节点的颜色不是红色就是黑色。可以把一棵红黑树视为一棵扩充二叉树,用外部节点表示空指针。其特性描述如下:
根节点和所有外部节点的颜色是黑色。
从根节点到外部节点的途中没有连续两个节点的颜色是红色。
特性 2 意味着如果一个节点的颜色是红色,则它的左、右孩子的颜色必然是黑色。
对任一节点,所有从该节点到外部节点的路径上都有相同数目的黑色节点。
从红黑树中任一节点 x 出发(不包括节点 x),到达任一个外部节点的路径上的黑节点个数叫做节点 x 的黑高度,亦称为节点的阶,记作 bh(x)
。红黑树的黑高度定义为其根节点的黑高度。
下图所示的二叉搜索树就是一棵红黑树,节点上边的数字为该节点的黑高度。
结论一:对任一节点 x,假设它的黑高度为 r,那么从节点 x 到任一外部节点的路径长度为 r ~ 2r。
注意:从节点 x 到任一外部节点的路径长度为该路径上的指针的个数。
结论二:假设 h 是一棵红黑树的高度(不包括外部节点),n 是红黑树中内部节点的个数, r 是红黑树的黑高度,则以下关系式成立:
(1) ;
(2) ;
(3) 。
证明:
(1) 由结论一即可证明。上图所示的红黑树的高度(不计外部节点)为 2r = 4。
(2) 因为红黑树的黑高度为 r,即从红黑树的第 1 层到第 r 层没有外部节点,所以内部节点的总数至少为 。在上图所示的红黑树中,红黑树的黑高度为 r = 2,第 1 层和第 2 层总共有 个内部节点,而在第 3 层和第 4 层还有 7 个节点,则有 。
(3) 由 (2) 可得 ,结合 (1),有 。
由此也可以知道红黑树的搜索、插入、删除操作的时间复杂度为 O()。
enum Color
{
RED,
BLACK
};
template
struct RBTNode
{
RBTNode* _left;
RBTNode* _right;
RBTNode* _parent;
std::pair _kv;
Color _clr;
RBTNode(const std::pair& kv = std::pair(), Color clr = RED)
: _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _clr(clr)
{ }
};
如果插入前树非空,若新节点被染成黑色,将违反红黑树的特性 3,即所有从根节点到外部节点的路径上的黑色节点个数变得不相等了,因此如果插入前树非空,新插入的节点应该被然成红色。
如果新插入的节点的父节点是黑色节点,则没有违反红黑树的任何特性,不需要做调整,即插入结束;如果新插入的节点的父节点是红色节点,则违反了红黑树的特性 2,即出现了连续两个红色节点,需要做平衡调整。
设 *cur
为当前节点,它和它的父结点 *parent
都是红色节点,它的祖父节点 *grandparent
是黑色节点,即出现了连续两个红色节点的情形,此时通过考查其叔叔节点 *uncle
做相应的平衡调整。
如果 *uncle
是红色节点,则将 *parent
和 *uncle
改为黑色节点,将 *grandparent
改为红色节点。例如:
如果
*grandparent
是根节点,则将其再改为黑色节点,调整完成。如果
*grandparent
是子树的根,则其父节点也可能是红色节点,所以还需要继续往上调整。
如果 *uncle
不存在或者是黑色节点,此时又有以下 4 种情况:
(1) *parent
是 *grandparent
的左孩子,*cur
是 *parent
的左孩子。在这种情况下,只要做一次右单旋,再将 *parent
改为黑色节点,*grandparent
改为红色节点,就可恢复红黑树的特性,并结束平衡调整。
(2) *parent
是 *grandparent
的左孩子,*cur
是 *parent
的右孩子。在这种情况下,只要做一次先左后右的双旋转,再将 *cur
改为黑色节点,*grandparent
改为红色节点,就可恢复红黑树的特性,并结束平衡调整。
(3) *parent
是 *grandparent
的右孩子,*cur
是 *parent
的右孩子。
(4) *parent
是 *grandparent
的右孩子,*cur
是 *parent
的左孩子。
情况 (2)、(3) 和情况 (1)、(2) 是镜像的。
#pragma once
#include
enum Color
{
RED,
BLACK
};
template
struct RBTNode
{
RBTNode* _left;
RBTNode* _right;
RBTNode* _parent;
std::pair _kv;
Color _clr;
RBTNode(const std::pair& kv = std::pair(), Color clr = RED)
: _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _clr(clr)
{ }
};
template
class RBT
{
typedef RBTNode Node;
public:
RBT() : _root(nullptr) { }
~RBT()
{
Destroy(_root);
}
bool isValidRBT()
{
if (_root == nullptr)
return true;
if (_root->_clr == RED)
return false;
// 获取从根节点到任一外部节点的路径上的黑色节点的个数
size_t blackCount = 0;
Node* cur = _root;
while (cur)
{
if (cur->_clr == BLACK)
++blackCount;
cur = cur->_left;
}
size_t k = 0; // k 用来路径种的黑色节点个数
return _isValidRBT(_root, blackCount, k);
}
bool insert(const std::pair& kv)
{
if (_root == nullptr)
{
_root = new Node(kv, BLACK);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
parent = cur;
if (kv.first < cur->_kv.first)
cur = cur->_left;
else if (kv.first > cur->_kv.first)
cur = cur->_right;
else
return false;
}
// 如果插入前树非空,新插入的节点应该是红色节点
cur = new Node(kv, RED);
cur->_parent = parent;
// 出现连续两个红色节点的情形
while (parent && parent->_clr == RED)
{
Node* grandparent = parent->_parent;
Node* uncle;
if (grandparent->_left == parent)
uncle = grandparent->_right;
else
uncle = grandparent->_left;
// 如果 *uncle 是红色节点
if (uncle && uncle->_clr == RED)
{
parent->_clr = uncle->_clr = BLACK;
grandparent->_clr = RED;
cur = grandparent;
parent = cur->_parent;
}
else // 如果 *uncle 不存在或者是黑色节点
{
if (grandparent->_left == parent && parent->_left == cur)
{
LL(grandparent);
parent->_clr = BLACK;
grandparent->_clr = RED;
}
else if (grandparent->_right == parent && parent->_right == cur)
{
RR(grandparent);
parent->_clr = BLACK;
grandparent->_clr = RED;
}
else if (grandparent->_left == parent && parent->_right == cur)
{
LR(grandparent);
cur->_clr = BLACK;
grandparent->_clr = RED;
}
else if (grandparent->_right == parent && parent->_left == cur)
{
RL(grandparent);
cur->_clr = BLACK;
grandparent->_clr = RED;
}
break;
}
}
_root->_clr = BLACK;
return true;
}
private:
void Destroy(Node*& root)
{
// 【注意:root 为 _root 或者某个节点的左或右指针的引用】
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
bool _isValidRBT(Node* root, size_t blackCount, size_t k)
{
if (root == nullptr)
{
if (blackCount == k)
return true;
else
return false; // 违反了特性 3
}
if (root->_clr == BLACK)
{
++k;
}
else
{
if (root->_parent && root->_parent->_clr == RED)
return false; // 违反了特性 2
}
return _isValidRBT(root->_left, blackCount, k)
&& _isValidRBT(root->_right, blackCount, k);
}
void LL(Node* parent)
{
Node* cur = parent->_left;
Node* curRight = cur->_right;
parent->_left = curRight;
if (curRight)
curRight->_parent = parent;
cur->_right = parent;
Node* tmp = parent->_parent;
parent->_parent = cur;
cur->_parent = tmp;
if (tmp == nullptr)
{
_root = cur;
}
else
{
if (tmp->_left == parent)
tmp->_left = cur;
else
tmp->_right = cur;
}
}
void RR(Node* parent)
{
Node* cur = parent->_right;
Node* curLeft = cur->_left;
parent->_right = curLeft;
if (curLeft)
curLeft->_parent = parent;
cur->_left = parent;
Node* tmp = parent->_parent;
parent->_parent = cur;
cur->_parent = tmp;
if (tmp == nullptr)
{
_root = cur;
}
else
{
if (tmp->_left == parent)
tmp->_left = cur;
else
tmp->_right = cur;
}
}
void LR(Node* parent)
{
Node* cur = parent->_left;
RR(cur);
LL(parent);
}
void RL(Node* parent)
{
Node* cur = parent->_right;
LL(cur);
RR(parent);
}
private:
Node* _root;
};
#include "RBT.h"
#include
#include
#include
using namespace std;
void TestRBT()
{
srand((unsigned int)time(0));
size_t N = 10000;
vector v;
for (size_t i = 0; i < N; ++i)
{
v.push_back(rand());
}
RBT t;
for (const auto& e : v)
{
t.insert(make_pair(e, e));
if (!t.isValidRBT())
cout << "插入操作有误!" << endl;
}
cout << "插入完成~" << endl;
}
int main()
{
TestRBT();
return 0;
}
红黑树和 AVL 树的增删查改的效率都很高,时间复杂度为 O(),但红黑树不追求极致的平衡,其只需保证最长路径不超过最短路径的 2 倍,相对而言,降低了平衡旋转的次数,所以在经常进行增删的结构中,红黑树的性能比 AVL 树更好,并且红黑树的实现更简单,所以在实际应用中,使用红黑树的也更多。