一,红黑树
1 红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
2 红黑树的性质
(1) 每个结点不是红色就是黑色
(2 ) 根节点是黑色的
(3) 如果一个节点是红色的,则它的两个孩子结点是黑色的
(4)对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 (5)每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
3 红黑树节点的定义
//节点颜色
enum Color
{
RED,
BLACK
};
//红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
RBTreeNode(const ValueType& data=ValueType(),Color color=RED)
: _pLeft(nullptr),_pReft(nullptr),_pParent(nullptr)
,_data(data),_color(color)
{}
RBTreeNode<ValueType>* _pLeft; //节点的左孩子
RBTreeNode<ValueType>* _pReft; //节点的右孩子
RBTreeNode<ValueType>* _pParent; //节点的双亲
ValueType _data; //节点的值域
Color _color; //节点的颜色
}
4 红黑树结构
为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为根节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:
:
5 红黑树的插入操作
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步
(1) 按照二叉搜索的树规则插入新节点
template<class ValueType>
2 class RBTree
3 {
4 //……
5 bool Insert(const ValueType& data)
6 {
7 PNode& pRoot = GetRoot();
8 if (nullptr == pRoot)
9 {
10 pRoot = new Node(data, BLACK);
11 // 根的双亲为头节点
12 pRoot->_pParent = _pHead;
13 _pHead->_pParent = pRoot;
14 }
15 else
16 {
17 // 1. 按照二叉搜索的树方式插入新节点
18 // 2. 检测新节点插入后,红黑树的性质是否造到破坏,
19 // 若满足直接退出,否则对红黑树进行旋转着色处理
20 }
21
22 // 根节点的颜色可能被修改,将其改回黑色
23 pRoot->_color = BLACK;
24 _pHead->_pLeft = LeftMost();
25 _pHead->_pRight = RightMost();
26 return true;
27 }
28 private:
29 PNode& GetRoot(){ return _pHead->_pParent;}
30 // 获取红黑树中最小节点,即最左侧节点
31 PNode LeftMost();
32 // 获取红黑树中最大节点,即最右侧节点
33 PNode RightMost();
34 private:
35 PNode _pHead;
36 };
(2). 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点情况一: cur为红,p为红,g为黑,u存在且为红
a 情况一: cur为红,p为红,g为黑,u存在且为红
b 情况二: cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色--p变黑,g变红
情况三: cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
bool Insert(const ValueType &data)
{ //...
// 新节点插入后,如果其双亲节点的颜色为红色,则违反性质3:不能有连在一起的红色结点
while(pParent && pParent->_color==RED)
{
// 注意:grandFather一定存在
// 因为pParent存在,且不是黑色节点,则pParent一定不是根,则其一定有双亲
PNode grandFather = pParent->_pParent;
// 先讨论左侧情况
if(pParent==grandFather->_pLeft)
{
PNode uncle =grandFather->-pRight;
// 情况三:叔叔节点存在,且为红
if(uncle && uncle->_color==RED)
{
pParent->_color=BLACK;
uncle->_color=BLACK;
grandFather->_color=RED;
pCur=grandFather;
pParent=pCur->pParent;
}
else
{
// 情况五:叔叔节点不存在,或者叔叔节点存在且为黑
if(pCur==pParent->_pRight)
{
_RotateLeft(pParent);
swap(pParent,pCur);
}
// 情况五最后转化成情况四
grandFather->_color=RED;
pParent->color=BLACK;
_RotateRIght(grandFather);
}
}
else
{
//右侧
}
}
//...
}
动态效果演示:
以升序(降序)插入构建红黑树
随机插入构建红黑树
6 红黑树的验证
(1). 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
(2). 检测其是否满足红黑树的性质
1 bool IsValidRBTree()
2 {
3 PNode pRoot = GetRoot();
4 // 空树也是红黑树
5 if (nullptr == pRoot)
6 return true;
7
8 // 检测根节点是否满足情况
9 if (BLACK != pRoot->_color)
10 {
11 cout << "违反红黑树性质二:根节点必须为黑色" << endl;
12 return false;
13 }
14
15 // 获取任意一条路径中黑色节点的个数
16 size_t blackCount = 0;
17 PNode pCur = pRoot;
18 while (pCur)
19 {
20 if (BLACK == pCur->_color)21 blackCount++;
22
23 pCur = pCur->_pLeft;
24 }
25
26 // 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数27 size_t k = 0;28 return _IsValidRBTree(pRoot, k, blackCount);
29 }
30
31 bool _IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount)
32 {
33 //走到null之后,判断k和black是否相等
34 if (nullptr == pRoot)35 {
36 if (k != blackCount)
37 {
38 cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
39 return false;
40 }
41 return true;
42 }
43
44 // 统计黑色节点的个数
45 if (BLACK == pRoot->_color)
46 k++;4748 // 检测当前节点与其双亲是否都为红色
49 PNode pParent = pRoot->_pParent;
50 if (pParent && RED == pParent->_color && RED == pRoot->_color)
51 {
52 cout << "违反性质三:没有连在一起的红色节点" << endl;
53 return false;
54 }
55
56 return _IsValidRBTree(pRoot->_pLeft, k, blackCount) &&
57 _IsValidRBTree(pRoot->_pRight, k, blackCount);
58 }
7 红黑树的删除
参考 http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html
http:// http://blog.youkuaiyun.com/chenhuajie123/article/details/11951777
8 红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( ),红黑树不追求绝对平衡,其 只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构 中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多
9 红黑树的应用
1. C++ STL库 -- map/set、mutil_map/mutil_set
2. Java 库
3. linux内核
4. 其他一些库
二,红黑树模拟实现STL中的map和set
1 红黑树的迭代器
迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考 虑以前问题
(1) begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一 个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点 (最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通 的,因为对end()位置的迭代器进行--操作,必须要能找最后一个元素,此处就不行,因此最好的方式是 将end()放在头结点的位置:
(2)operator++()与operator--()
// 找迭代器的下一个节点,下一个节点肯定比其大
void Increasement()
{
//分两种情况讨论:_pNode的右子树存在和不存在
//右子树存在
if(_pNode->_pRight)
{
//右子树中最小的节点,即右子树中最左侧节点
_pNode=_pNode->pRight;
while(_pNode->_pLeft)
_pNode=_pNode->_pLeft;
}
else
{
// 右子树不存在,向上查找,直到_pNode != pParent->right
PNode pParent=_pNode->_pParent;
while(pParent->pRight==_pNode)
{
_pNode =pParent;
pParent=_pNode->_pParent;
}
//特殊情况:根节点没有右子树
if(_pNode->_pRight !=pParent)
_pNode=pParent;
}
}
// 获取迭代器指向节点的前一个节点
void Decreasement()
{
//分三种情况讨论:_pNode 在head的位置,_pNode 左子树存在,_pNode 左子树不存在
// 1. _pNode 在head的位置,--应该将_pNode放在红黑树中最大节点的位置
if(_pNode->_pParent->_pParent == _pNode && _pNode->_color == RED)
_pNode = _pNode->_pRight;
else if(_pNode->_pLeft)
{
// 2. _pNode的左子树存在,在左子树中找最大的节点,即左子树中最右侧节点
_pNode = _pNode->_pLeft;
while(_pNode->_pRight)
_pNode = _pNode->_pRight;
}
else
{
// _pNode的左子树不存在,只能向上找
PNode pParent = _pNode->_pParent;
while(_pNode == pParent->_pLeft)
{
_pNode = pParent;
pParent = _pNode->_pParent;
}
_pNode = pParent;
}
}
2 改造红黑树
// 因为关联式容器中存储的是<key, value>的键值对,因此
// k为key的类型,
// ValueType: 如果是map,则为pair<K, V>; 如果是set,则为k
// KeyOfValue: 通过value来获取key的一个仿函数类
template<class K, class ValueType, class KeyOfValue>
class RBTree
{
typedef RBTreeNode<ValueType> Node;
typedef Node* PNode;
public:
typedef RBTreeIterator<ValueType, ValueType*, ValueType&> Iterator;
public:
RBTree();
~RBTree()
/
// Iterator
Iterator Begin(){ return Iterator(_pHead->_pLeft);}
Iterator End(){ return Iterator(_pHead);}
//
// Modify
pair<Iterator, bool> Insert(const ValueType& data)
{
// 插入节点并进行调整
// 参考上文...
return make_pair(Iterator(pNewNode), true);
}
// 将红黑树中的节点清空
void Clear();
Iterator Find(const K& key);
//
// capacity
size_t Size()const;
bool Empty()const;
// ……
private:
PNode _pHead;
size_t _size; // 红黑树中有效节点的个数
};
3 map的模拟实现
map的底层结构就是红黑树,因此在map中直接封装一棵红黑树,然后将其接口包装下即可
namespace bite
{
template<class K, class V>
class map
{
typedef pair<K, V> ValueType;
// 作用:将value中的key提取出来
struct KeyOfValue
{
const K& operator()(const ValueType& v)
{ return v.first;}
};
typedef RBTree<K, ValueType, KeyOfValue> RBTree;
public:
typedef typename RBTree::Iterator iterator;
public:
map(){}
/
// Iterator
iterator begin(){ return _t.Begin();}
iterator end(){ return _t.End();}
/
// Capacity
size_t size()const{ return _t.Size();}
bool empty()const{ return _t.Empty();}
/
// Acess
V& operator[](const K& key)
{ return (*(_t.Insert(ValueType(key, V()))).first).second;}
const V& operator[](const K& key)const;
// modify
pair<iterator, bool> insert(const ValueType& data) { return _t.Insert(data);}
void clear(){ _t.Clear();}
iterator find(const K& key){ return _t.Find(key);}
private:
RBTree _t;
};
}
4set的模拟实现
set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来(具体实现可参考map)。
namespace bit
{
template<class K>
class set
{
typedef K ValueType;
// 作用是:将value中的key提取出来
struct KeyOfValue
{
const K& operator()(const ValueType& key)
{ return key;}
};
// 红黑树类型重命名
typedef RBTree<K, ValueType, KeyOfValue> RBTree;
public:
typedef typename RBTree::Iterator iterator;
public:
Set(){}
/
// Iterator
iterator Begin();
iterator End();
/
// Capacity
size_t size()const;
bool empty()const;
// modify
pair<iterator, bool> insert(const ValueType& data)
{
return _t.Insert(data);
}
void clear();
iterator find(const K& key);
private:
RBTree _t;
};
}