概述:R-B Tree,又称为“红黑树”。本文参考了《算法导论》中红黑树相关知识,加之自己的解,然后以图文的形式对红黑树进行说明。本文的主要内容包括:红黑树的特性,红黑树的时间复杂度和它的证明,红黑树的左旋、右旋、插入等操作。
1 R-B Tree简介
R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
2 R-B Tree时间复杂度
红黑树的时间复杂度为: O(lgn)
定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).
3 R-B Tree基本操作
R-B Tree的基本操作是添加、删除。 添加和删除操作,都会用到两个基本的方法:左旋 和 右旋,统称为旋转。旋转是为了保持红黑树的特性而提供的辅助方法,因为当我们进行添加、删除节点时,可能改变红黑树的特性(例如,删除一个黑色节点之后,就不满足“从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点”这个特性);这里,我们就需要旋转方法的辅助来让树保持红黑树的特性。
向一颗含有n个节点的红黑树中插入一个节点,可以在时间O(lgn)内完成。
将节点z插入红黑树T内。需要执行的操作依次时:首先,将T当作一颗二叉树,将z插入;然后,将z着色为红色;最后,通过RB-INSERT-FIXUP来对节点重新着色并旋转,以此来保证删除节点后的树仍然是一颗红黑树。
(01) 将T当作一颗二叉树,将z插入。
因为红黑树本身就是一颗二叉树,所以,我们可以根据二叉树的性质将z插入。
(02) 将z着色为红色。
在介绍为什么将则着色为红色之前,我们重新温习一下红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
将插入的节点着色为红色,不会违背“特性(5)”;而若将插入的节点着色为黑色,会违背该特性。
(03) 通过RB-INSERT-FIXUP来对节点重新着色并旋转。
因为(02)中插入一个红色节点之后,虽然没有违背“特性(5)”,但是却可能违背了其它特性(例如,若被插入节点的父节点也是红色;插入后,则违背了“特性(4)”)。我们需要通过RB-INSERT-FIXUP进行节点颜色的调整以及旋转等工作,让树仍然是一颗红黑树。
总的来说:当节点z被着色为红色节点,并插入二叉树时,有三种情况。
情况一:被插入的节点是根节点。
直接把此节点涂为黑色。
情况二:被插入的节点的父节点是黑色。
什么也不需要做。节点被插入后,仍然是红黑树。
情况三:被插入的节点的父节点是红色。
那么,该情况与红黑树的“特性(5)”相冲突。情况三包含了“Case 1”、“Case 2” 和“Case 3”三种情况,情况三的目的是恢复红黑树的特性,它的处理思想是:将红色的节点移到根节点;然后,将根节点设为黑色。下面介绍情况三的三种情况。
Case 1 现象说明:当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。
Case 1 处理策略:
(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
Case 2 现象说明:当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子
Case 2 处理策略:
(01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。
Case 3:叔叔是黑色,当前节点是做孩子
Case 3:叔叔是黑色,且当前节点是左孩子
Case 3 现象说明:当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子
Case 3 处理策略:
(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。
#include<iostream>
using namespace std;
enum Color
{
BLACK,
RED
};
template<class K,class V>
struct RBTreeNode
{
RBTreeNode(const K& key,const V&value,const Color col = RED)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_col(col)
,_key(key)
,_value(value)
{}
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Color _col;
K _key;
V _value;
};
template<class K,class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
RBTree()
:_root(NULL)
{}
bool Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value, BLACK);
return true;
}
Node* parent = NULL;
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
break;
}
}
cur = new Node(key, value, RED);
if (parent->_key <key)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//调色
while (cur != _root && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
//当叔叔节点为黑色,且S为F的右孩子,处理步骤;1 以父节点进行左旋 2将父节点变黑祖父节点变红,3然后进行右旋
else
{
if (cur == parent->_right)
{
RotateL(parent);
swap(cur, parent);
}
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
}
else //往右子树插
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
RotateR(parent);
swap(cur, parent);
}
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
}
}
_root->_col = BLACK;
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
protected:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
{
subRL->_parent = parent;
}
subR->_left = parent;
subR->_parent = parent->_parent;
parent = subR;
if (parent->_parent == NULL)
{
_root = parent;
}
else
{
if (parent->_key < parent->_parent->_key)
{
parent->_parent->_left = parent;
}
else
{
parent->_parent->_right = parent;
}
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if(subLR)
{
subLR->_parent = parent;
}
subL->_right = parent;
subL->_parent = parent->_parent;
parent->_parent = subL;
parent = subL;
if (parent->_parent == NULL)
{
_root = parent;
}
else
{
if (parent->_key < parent->_parent->_key)
{
parent->_parent->_left = parent;
}
else
{
parent->_parent->_right = parent;
}
}
}
void _InOrder(Node*& root)
{
if (root == NULL)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
protected:
Node* _root;
};
void TestRBTree()
{
RBTree<int, int> t1;
int a[10] = { 5, 2, 9, 6, 7, 3, 40, 1, 8 };
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
{
t1.Insert(a[i], i);
t1.InOrder();
}
cout << "IsBalanceTree:" << t1.IsBalanceTree() << endl;
}
int main()
{
TestRBTree();
system("pause");
return 0;
}
4 运行结果
5 红黑树的应用
红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。
例如,Java中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。
这里大致介绍下,红黑树和AVL树的差异。AVL树也是特殊的二叉树,它的特性是“任何节点的左右子树的高度之差不超过1”。基本上,用到红黑树的地方都可以用AVL树(自平衡二叉查找树)去替换。但是一般情况下,在执行添加、删除节点时,AVL树比红黑树执行的操作更多一些,效率更低一些;而且红黑树也是相对平衡的二叉树(从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点)。因此,红黑树的效率会高更一点。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。