文章目录
一、什么是RB-tree
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。所谓RB-tree,不仅是一个二叉搜索树,而且必须满足以下规则:
- 每个节点不是红色就是黑色
- 根节点为黑色
- 不存在两个连续的红色节点
- 任一节点至NULL(树尾端)的任何路径,所含之黑节点数必须相同
二、RB-tree的用途
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。红黑树在Linux操作系统中运用广泛。
- 1、进程调度:Linux内核使用红黑树来管理进程调度。每个运行中的进程都有一个进程控制块(进程描述符struct task_struct),这些块通过红黑树进行组织和调度,进程们都在进程调度器的红黑树中维护一个节点,通过对红黑树进行适当的旋转和平衡,这样可以根据进程的优先级和其他因素轻松地查找和选择下一个要运行的进程。红黑树的平衡特性确保了进程调度的高效性。
- 2、文件系统:Linux文件系统中的inode对象也使用红黑树来组织和管理。这允许系统快速地查找文件和目录,并支持高效的文件系统操作。例如,dentry 结构表示一个目录项,这些目录项可以添加到红黑树中,每个目录都可以用一个红黑树来组织其中的文件和子目录,以便高效地查找和访问它们。
- 3、定时器管理:内核中的定时器管理通常也使用红黑树来实现。这可以用于管理各种定时事件,如进程超时、I/O超时等。红黑树的自平衡特性确保了定时器事件的高效处理,提高定时器查找和触发的效率。例如,struct timer_list 结构体表示一个定时器,这些定时器可以添加到红黑树中,以便按计划触发回调函数,其中的 expires 字段被用作关键字,使得内核能够在 O(log n) 时间内查找和触发定时器。
- 4、虚拟内存管理:红黑树可用于管理虚拟内存中的页表项,用于快速查找和映射虚拟地址到物理地址的操作。
- 5、设备驱动:Linux内核中的设备驱动程序通常也使用红黑树来维护设备列表。这样可以在设备数量增加时提供快速的查找和访问。例如 USB 设备管理和块设备驱动程序可以使用红黑树来管理设备列表。
- 6、系统调用表:红黑树可以用于维护系统调用表,以便系统可以快速查找和执行系统调用。
- 7、网络子系统:在网络子系统中,红黑树通常用于管理连接状态、套接字或路由表,以实现高性能和高效的网络通信,加速查找和操作。路由表中的路由项可以根据目的地址添加到红黑树中,以便快速查找目标主机的路由信息然后发送对应路由的数据包。这对于数据包的高效路由非常重要。
- 8、内存管理:红黑树也可用于内存分配和管理,以跟踪可用内存块或页面。红黑树在内核中用于管理内存页后,可以有效地分配和释放内存。这有助于内核避免内存碎片,以及高效地查找可用的内存页。例如,struct page 结构用于表示内存页,这些页可以根据地址添加到红黑树中,以便高效地查找可用的内存页。
- 9、调度器:红黑树被用于调度器的实现,根据优先级和其他因素高效地选择要运行的任务。进程控制块(struct task_struct)通过红黑树进行排序,根据进程的优先级和其他因素选择下一个要运行的任务。
- 10、虚拟文件系统:Linux 内核中的虚拟文件系统(如ext4、XFS、VFS)使用红黑树来管理打开的文件,管理目录项和索引节点,以及与文件路径相关的操作。struct file 结构表示一个打开的文件,这些文件可以添加到红黑树中,以便高效地查找和操作文件。
- 11、安全策略:安全模块(如 SELinux、AppArmor)使用红黑树来存储安全策略规则,以加速访问和决策。
- 12、事件通知:Linux 内核中的事件通知机制(如 epoll)使用红黑树来管理文件描述符,以便高效地检查和响应事件。
三、STL中的RB-tree设计思路
1.插入节点
假设我们为下图的RB-tree 分别插入3,8,35,75,根据二叉搜索树的规则,这四个新节点的落脚处应该下图所示。是的,它们都破坏了RB-tree的规则,因此我们必须调整树形,也就是旋转树形并改变节点颜色。
为了方便讨论,我们为一些特殊节点定义一些代名。假设新节点为X,其父节点为P,伯父节点(父节点的兄弟)为S,曾祖父节点为GG。
现在,根据二叉搜索树的规则,新节点X必为叶节点。根据红黑树的规则4,X必为红。若P亦为红(这就违反了规则3,必须调整树形),则G必为黑(必须遵循规则3)。
于是X的插入位置及外围节点的颜色,有以下三种考虑(《STL源码剖析》分为4种,可合并为3种):
a). S为黑且X为外侧插入
对此情况,我们先对P,G做一次单旋转,并改变P,G颜色,即可重新满足红黑树的规则3。
b). S为黑且X为内侧插入
对此情况,我们必须先对P,X做一次单旋转并更改G,X颜色,再将结果对G做一次单旋转,即可再次满足红黑树规则3。
c). S为红且X 为外侧插入
对此情况,先对P和G做一次单旋转,并改变X的颜色。如果GG为黑,一切搞定;如果GG为红,还得继续往上调整,直到不再有父子连续为红的情况。
2.一个由上而下的程序
为了避免状况3“父子节点皆为红色”的情况持续向RB-tree的上层结构发展,形成处理时效上的瓶颈,我们可以施行一个由上而下的程序:假设新增节点为A,那么就延着A的路径,只要看到有某节点X的两个子节点皆为红色,就把X改为红色,并把两个子节点改成黑色。
但是如果X的父节点P亦为红色(此时S绝不可能为红),就得像状况1一样地做一次单旋转并改变颜色,或是像状况2一样地做一次双旋转并改变颜色。
在此之后,节点35的插入就很单纯了:要么直接插入,要么插入后(若X节点为红)再一次旋转(单双皆可能)即可。如下图所示:
3.RB-tree的节点设计
为了有更大的弹性,节点分为两层:
下面是RB-tree 的节点图标,其中value_field 填为10:
4.RB-tree 的迭代器
为了更大的弹性,SGI 将 RB-tree 迭代器实现为两层:
RB-tree 迭代器属于双向迭代器,但不具备随机定位能力,其提领操作和成员访问操作与list十分近似,较为特殊的是其前进和后退操作。注意,RB-tree 迭代器的前进操作operator++()调用了基层迭代器的increment() ,RB-tree 迭代器的后退操作operator–() 调用了基层迭代器的decrement()。
RB-tree 的正规迭代器如下:
而基层迭代器为:
图中蓝色方块中的代码对应下图中的场景:
5.RB-tree 的数据结构
6.元素操作
只谈元素的插入。RB-tree 提供两种插入操作:insert_unique( ) 和 insert_equal( ) ,前者表示被插入节点的键值(key)在整棵树中必须独一无二,后者表示被插入节点的键值在整棵树中可以重复。RB-tree 一开始即要求用户必须明确设定的KeyOfValue 仿函数,因此,从实值(value)中取出键值(key)是毫无问题的。
但真正的插入执行程序:_ _insert( )
调整RB-tree(旋转及改变颜色):任何插入操作,于节点插入完毕后,都要做一次调整操作。
inline __rb_tree_node_base*
__rb_tree_rebalance_for_erase(__rb_tree_node_base* z,
__rb_tree_node_base*& root,
__rb_tree_node_base*& leftmost,
__rb_tree_node_base*& rightmost)
{
__rb_tree_node_base* y = z;
__rb_tree_node_base* x = 0;
__rb_tree_node_base* x_parent = 0;
if (y->left == 0) // z has at most one non-null child. y == z.
x = y->right; // x might be null.
else
if (y->right == 0) // z has exactly one non-null child. y == z.
x = y->left; // x is not null.
else { // z has two non-null children. Set y to
y = y->right; // z's successor. x might be null.
while (y->left != 0)
y = y->left;
x = y->right;
}
if (y != z) { // relink y in place of z. y is z's successor
z->left->parent = y;
y->left = z->left;
if (y != z->right) {
x_parent = y->parent;
if (x) x->parent = y->parent;
y->parent->left = x; // y must be a left child
y->right = z->right;
z->right->parent = y;
}
else
x_parent = y;
if (root == z)
root = y;
else if (z->parent->left == z)
z->parent->left = y;
else
z->parent->right = y;
y->parent = z->parent;
__STD::swap(y->color, z->color);
y = z;
// y now points to node to be actually deleted
}
else { // y == z
x_parent = y->parent;
if (x) x->parent = y->parent;
if (root == z)
root = x;
else
if (z->parent->left == z)
z->parent->left = x;
else
z->parent->right = x;
if (leftmost == z)
if (z->right == 0) // z->left must be null also
leftmost = z->parent;
// makes leftmost == header if z == root
else
leftmost = __rb_tree_node_base::minimum(x);
if (rightmost == z)
if (z->left == 0) // z->right must be null also
rightmost = z->parent;
// makes rightmost == header if z == root
else // x == z->left
rightmost = __rb_tree_node_base::maximum(x);
}
if (y->color != __rb_tree_red) {
while (x != root && (x == 0 || x->color == __rb_tree_black))
if (x == x_parent->left) {
__rb_tree_node_base* w = x_parent->right;
if (w->color == __rb_tree_red) {
w->color = __rb_tree_black;
x_parent->color = __rb_tree_red;
__rb_tree_rotate_left(x_parent, root);
w = x_parent->right;
}
if ((w->left == 0 || w->left->color == __rb_tree_black) &&
(w->right == 0 || w->right->color == __rb_tree_black)) {
w->color = __rb_tree_red;
x = x_parent;
x_parent = x_parent->parent;
} else {
if (w->right == 0 || w->right->color == __rb_tree_black) {
if (w->left) w->left->color = __rb_tree_black;
w->color = __rb_tree_red;
__rb_tree_rotate_right(w, root);
w = x_parent->right;
}
w->color = x_parent->color;
x_parent->color = __rb_tree_black;
if (w->right) w->right->color = __rb_tree_black;
__rb_tree_rotate_left(x_parent, root);
break;
}
} else { // same as above, with right <-> left.
__rb_tree_node_base* w = x_parent->left;
if (w->color == __rb_tree_red) {
w->color = __rb_tree_black;
x_parent->color = __rb_tree_red;
__rb_tree_rotate_right(x_parent, root);
w = x_parent->left;
}
if ((w->right == 0 || w->right->color == __rb_tree_black) &&
(w->left == 0 || w->left->color == __rb_tree_black)) {
w->color = __rb_tree_red;
x = x_parent;
x_parent = x_parent->parent;
} else {
if (w->left == 0 || w->left->color == __rb_tree_black) {
if (w->right) w->right->color = __rb_tree_black;
w->color = __rb_tree_red;
__rb_tree_rotate_left(w, root);
w = x_parent->left;
}
w->color = x_parent->color;
x_parent->color = __rb_tree_black;
if (w->left) w->left->color = __rb_tree_black;
__rb_tree_rotate_right(x_parent, root);
break;
}
}
if (x) x->color = __rb_tree_black;
}
return y;
}
四、模拟实现RB-tree
1.节点结构
template<class K ,class V>
struct RBTreeNode
{
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Color _col;
RBTreeNode(const pair<K,V>& kv)
:_kv(kv)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_col(RED)
{}
};
2.RB-tree 的实现
这里我们实现的和源码思路有一定差异:
- 没有采用一个header节点,让其left 指向左子树最左节点,right 指向右子树最右节点
- 我们没有采用全局调整函数来对树进行调整,而是将调整操作并入了inert 的操作
- 没有区分inert_unique( ),inert_equal( )
template<class K ,class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
public:
bool Insert(const pair<K,V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
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 = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first) { parent->_right = cur; cur->_parent = parent; }
else { parent->_left = cur; cur->_parent = parent; }
//调整树
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//情况1:uncle存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//uncle不存在或者为黑
{
if (cur == parent->_left)//情况2:右单旋
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情况3:先左单旋再右单旋
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else//parent == grandfather->_right
{
Node* uncle = grandfather->_left;
//情况1:uncle存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//uncle不存在或者为黑
{
// g
// f
// c
//
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// f
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//重新链接两处子树
parent->_right = subRL;
if (subRL) subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
//处理头结点关系
if (ppnode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent) ppnode->_left = subR;
else ppnode->_right = subR;
subR->_parent = ppnode;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//重新链接两处子树
parent->_left = subLR;
if (subLR) subLR->_parent = parent;
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//处理头结点关系
if (ppnode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent) ppnode->_left = subL;
else ppnode->_right = subL;
subL->_parent = ppnode;
}
}
void Inorder()
{
_Inorder(_root);
}
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_Inorder(root->_right);
}
bool Check(Node* root, int blackNum, const int ref)
{
if (root == nullptr)
{
//cout << blackNum << endl;
if (blackNum != ref)
{
cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "违反规则:出现连续红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
++blackNum;
}
return Check(root->_left, blackNum, ref)
&& Check(root->_right, blackNum, ref);
}
bool IsBalance()
{
if (_root == nullptr)
{
return true;
}
if (_root->_col != BLACK)
{
return false;
}
int ref = 0;
Node* left = _root;
while (left)
{
if (left->_col == BLACK)
{
++ref;
}
left = left->_left;
}
return Check(_root, 0, ref);
}
private:
Node* _root = nullptr;
};
void TestRBTree1()
{
//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
RBTree<int, int> t;
for (auto e : a)
{
/*if (e == 18)
{
int x = 0;
}*/
t.Insert(make_pair(e, e));
cout << "insert" << e << ":" << t.IsBalance() << endl;
}
t.Inorder();
cout << t.IsBalance() << endl;
}
void TestRBTree2()
{
srand(time(0));
const size_t N = 100000;
RBTree<int, int> t;
for (size_t i = 0; i < N; ++i)
{
size_t x = rand();
t.Insert(make_pair(x, x));
//cout << t.IsBalance() << endl;
}
//t.Inorder();
cout << t.IsBalance() << endl;
}
五、set
set 的特性是,所有元素都会根据元素的键值自动被排序。set的元素不像map那样可以同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。set不允许两个元素有相同的键值。当然,我们也不能通过set的迭代器来更改set的元素值。所以set<T>::iterator 被定义为底层RB-tree 的const_iterator。
标准的STL set 即以RB-tree 为底层机制。所以几乎所有的set 操作行为,都只是转调用RB-tree 的操作行为。
六、map
map 的特性是,所有元素都会根据元素的键值自动被排序。map 的所有元素都是pair,同时拥有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。map不允许两个元素拥有相同的键值。
下图是map 的架构:
在此针对insert( ) 函数进行一些说明:
此式将工作直接转给底层机制的RB-tree 的inert_unique( )函数去执行,返回值型别是一个pair ,由一个迭代器和一个bool 值组成,后者表示插入成功与否,成功的话前者即指向被插入的那个元素。
并对subscript(下标)操作符进行一些说明:
用法有两种,可能作为左值引用(内容可被修改),也可能作为右值引用(内容不可被修改),如:
map<string,int> simap;
simap[string("jjhou")] = 1; //左值引用
...
int num = simap[string["jjhou"]];//右值引用
而对于该返回值的理解,我们有必要好好分析一下:
首先,根据键值和实值做出一个元素,由于实值未知,所以产生一个与实值型别相同的暂时对象代替
value_type(K,T())
再将该元素插入到map里面去:
inset(value_type(K,T()))
插入操作失败返回一个pair ,其第一元素是个迭代器,指向插入妥当的新元素,或指向插入失败点(键值重复)的旧元素。注意,如果下标操作符作为左值运用(通常表示要添加新元素),我们正好以此“实值待填”的元素将位置卡好;如果下标操作符作为右值运用(通常表示要根据键值取实值),此时的插入操作所返回的pair的第一元素(是个迭代器)恰指向键值符合的旧元素。
现在我们取插入所返回的pair的第一个元素:
inset(value_type(K,T()))).first
这第一元素是个迭代器,指向被插入的元素。现在,提领该迭代器:
*(inset(value_type(K,T()))).first)
获得一个map元素,是一个由键值和实值组成的pair。取其第二元素,即为实值:
(*(inset(value_type(K,T()))).first)).second
注意,这个实值以 by reference 方式传递,所以它作为左值或右值都可以。
七、multiset
它与set唯一的差别在于它的插入操作采用的是底层机制RB-tree 的 insert_equal( )而非insert_unique( )。所以它允许键值重复。
八、multimap
它与map唯一的差别在于它的插入操作采用的是底层机制RB-tree 的 insert_equal( )而非insert_unique( )。所以它允许键值重复。