红黑树:
本质是一棵近似平衡的搜索树,当然,想要了解红黑树的前提是你足够了解搜索树和AVL(高度平衡的二叉搜索树);
红黑树的特征:
(注意,本文所讲内容时刻围绕这五条特征,请仔细阅读)
1. 每个节点,不是红色就是黑色的
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个子节点是黑色的
4. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
5. 空节点都是黑色的
下面是一颗典型的红黑树特例,结合红黑书的特点看图:
接下来的内容就围绕红黑树的插入结点展开;
注意:时刻保持红黑树的特征;
红黑树结点的结构组成:
enum Color
{
RED,
BLACK
};
template<class K,class V>
struct RBNode
{
RBNode<K,V>* _left;
RBNode<K,V>* _right;
RBNode<K,V>* _parent;
K _key;//关键码,在树中具有惟一性;
V _value;//携带的值,可有可无;
Color _color;//颜色
RBNode(){}
RBNode(const K& key,const V& value)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_key(key)
,_value(value)
,_color(RED)//新入结点初始颜色为红
{}
};
1.分析我们插入节点的颜色应该初始为黑还是红?
答案是红,为什么?
结合红黑树的特征,每条路径上的黑色节点的数目必须相等,如果新插入结点的颜色为黑色的话,岂不是每次都得进行调整,维持红黑树的特性;
而新插入节点的颜色如果为红色的话,则可以省事一些;
2.寻找插入结点的位置,和搜索树一样(如果不知道搜索树,赶紧了结),先将结点连在树上,再进行调整;这里只列出代码;
//首先依据搜索数特性找到插入元素的位置;
if(_root == NULL)
{
_root = new Node(key,value);
_root->_color = BLACK;
return true;
}
Node* cur = _root;
Node* parent = NULL;
while(cur)
{
parent = cur;
if(cur->_key < key)
cur = cur->_right ;
else if(cur->_key > key)
cur = cur->_left ;
//注意,如果元素的key已经存在,则插入失败;
else
return false;
}
//找到插入位置
cur = new Node(key,value);
if(parent->_key > key)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
(注意:接下来我们约定只以插入结点在根节点的左边为例,右边以此类推)
3.新结点已经连在了树上,现在要考虑的情况又变味怎样进行调整,我们在这里分为5种情况:
我们在这里进行图示的一些约定:‘c’ 代表新插入的结点或调整上来的结点,‘p’代表父亲结点,‘u’代表叔叔结点,‘g’代表组父节点;三角形代表一颗子树或者一颗空树,视情况而定!(至于什么是叔叔结点,父亲结点,祖父结点之类,相信大家观察一下就可以理解!)
注意:一切调整的前提都是建立在红黑树的特征被改变了的情况下!
(依据红黑树的特征)
1. 新入结点的是根节点,不需要进行调整,只要改变颜色为黑就好;
2. 新入结点的父节点为黑结点,不会改变红黑树的特征,不需要进行调整,颜色也不需要改变;
3. 新入结点的叔叔存在且为红结点(如图)
4. 叔叔结点不存在;
5. 叔叔结点存在且为黑;
4.上述的情况5又可以分为,新插入节点在父亲的左边或者右边两种情况;这就涉及到单旋和双旋的情况,知道AVL树的同学应该有些了解,但是这里的双旋又和AVL树的双旋略有不同;
上述五种情况只有后三种需要进行调整,调整的目的在于通过旋转维持红黑树的特征;
叔叔存在且为红的调整:只需要将U和P结点的颜色变为黑,g变为红即可,保证每条路径上的黑节点个数和以前一样,此时应把g当作新的C结点继续向上调整,因为,我们所给出的树可能只是一颗子树,如果g是根节点,那么只需要再把g的颜色变为黑即可;如下图:
-叔叔结点不存在的情况其实可以并入第三种情况的一种单旋的情况,区别在于,如果叔叔节点不存在,则C一定是新插入的结点,而叔叔结点存在且为黑,则C一定是调整上来的结点,;
结合上图的情况进行一次右单旋,g变为p的右,而原来p的右子树变为g的左子树,改变g和p的颜色,以保持红黑树的特征,如下图:
绿框的部分表示旋转的轴!
- 叔叔结点存在且为黑,我们直接选择这种情况中的新插入节点在父节点右边的情况,在父亲结点左边的情况类似叔叔结点为空的情况;
如下图,需要进行双旋的情况:
:先以p和c为轴,进行一次左单选,再以c和p为轴进行一次右单旋,这里需要注意的是,第一次旋转的时候,c和p的位置发生了变化,再最后改变g和p的颜色时,就有问题了,所以,在代码实现的时候,在第一次旋转之后,我们应该对p和c的指针进行交换,切记!!!
注意:一次的调整并不代表结束,因为g有可能一开始就是一颗子树,所以,需要继续向上进行判断调整,直到不用调整;
5.实现代码
在实现代码的时候,我还加入了IsBlance这个函数,来判断树是否符合红黑树的特征,判断的根据还是红黑树的五个特征;
完整代码的连接:RBTree 完整代码