性质
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 所有叶子都是黑色(叶子是NIL节点)。
性质4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
下面是一个具体的红黑树的图例:
这些约束确保了红黑树的关键特性: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
要知道为什么这些性质确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。
在很多树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点包含数据。用这种范例表示红黑树是可能的,但是这会改变一些性质并使算法复杂。为此,本文中我们使用 "nil 叶子" 或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好像同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。
操作
因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次。
插入
我们首先以二叉查找树的方法增加节点并标记它为红色。(如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。) 下面要进行什么操作取决于其他临近节点的颜色。同人类的家族树中一样,我们将使用术语叔父节点来指一个节点的父节点的兄弟节点。注意:
- 性质1和性质3总是保持着。
- 性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。
- 性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。
在下面的示意图中,将要插入的节点标为N,N的父节点标为P,N的祖父节点标为G,N的叔父节点标为U。在图中展示的任何颜色要么是由它所处情形这些所作的假定,要么是假定所暗含 (imply) 的。
对于每一种情形,我们将使用 C 示例代码来展示。通过下列函数,可以找到一个节点的叔父和祖父节点:
node* grandparent(node *n) { return n->parent->parent; } node* uncle(node *n) { if (n->parent == grandparent(n)->left) return grandparent(n)->right; else return grandparent(n)->left; }
情形1: 新节点N位于树的根上,没有父节点。在这种情形下,我们把它重绘为黑色以满足性质2。因为它在每个路径上对黑节点数目增加一,性质5符合。
void insert_case1(node *n) { if (n->parent == NULL) n->color = BLACK; else insert_case2(n); }
情形2: 新节点的父节点P是黑色,所以性质4没有失效(新节点是红色的)。在这种情形下,树仍是有效的。性质5也未受到威胁,尽管新节点N有两个黑色叶子子节点;但由于新节点N是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径同样数目的黑色节点,所以依然满足这个性质。
void insert_case2(node *n) { if (n->parent->color == BLACK) return; /* 树仍旧有效 */ else insert_case3(n); }
注意: 在下列情形下我们假定新节点的父节点为红色,所以它有祖父节点;因为如果父节点是根节点,那父节点就应当是黑色。所以新节点总有一个叔父节点,尽管在情形4和5下它可能是叶子节点。
情形3: 如果父节点P和叔父节点U二者都是红色,(此时新插入节点N做为P的左子节点或右子节点都属于情形3,这里右图仅显示N做为P左子的情形)则我们可以将它们两个重绘为黑色并重绘祖父节点G为红色(用来保持性质4)。现在我们的新节点N有了一个黑色的父节点P。因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行情形1的整个过程。(把G当成是新加入的节点进行各种情形的检查) |
void insert_case3(node *n) { if (uncle(n) != NULL && uncle(n)->color == RED) { n->parent->color = BLACK; uncle(n)->color = BLACK; grandparent(n)->color = RED; insert_case1(grandparent(n)); } else insert_case4(n); }
注意: 在余下的情形下,我们假定父节点P是其父亲G的左子节点。如果它是右子节点,情形4和情形5中的左和右应当对调。
情形4: 父节点P是红色而叔父节点U是黑色或缺少,并且新节点N是其父节点P的右子节点而父节点P又是其父节点的左子节点。在这种情形下,我们进行一次左旋转调换新节点和其父节点的角色; 接着,我们按情形5处理以前的父节点P以解决仍然失效的性质4。注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效。 |
void insert_case4(node *n) { if (n == n->parent->right && n->parent == grandparent(n)->left) { rotate_left(n->parent); n = n->left; } else if (n == n->parent->left && n->parent == grandparent(n)->right) { rotate_right(n->parent); n = n->right; } insert_case5(n); }
情形5: 父节点P是红色而叔父节点U是黑色或缺少,新节点N是其父节点的左子节点,而父节点P又是其父节点G的左子节点。在这种情形下,我们进行针对祖父节点G的一次右旋转; 在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色(如果P和G都是紅色就違反了性質4,所以G必須是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。 |
void insert_case5(node *n) { n->parent->color = BLACK; grandparent(n)->color = RED; if (n == n->parent->left && n->parent == grandparent(n)->left) { rotate_right(grandparent(n)); } else { /* Here, n == n->parent->right && n->parent == grandparent(n)->right */ rotate_left(grandparent(n)); } }
注意插入实际上是原地算法,因为上述所有调用都使用了尾部递归。
本篇文章并没有详细的讲解红黑树各方面的知识,只是以图形的方式对红黑树插入节点需要进行调整的过程进行的解释。
最近在看stl源码剖析,看到map底层红黑树的实现。为了加深对于红黑树的理解就自己动手写了红黑树插入的实现。关于红黑树插入节点后破坏红黑树性质的几种情况,可以在网上搜到很多相关的信息。下面用图说明插入新节点时红黑树所做的调整。插入的序列分别是30,40,50,20,35,10,11。
首先是插入30,由于现在红黑树是一棵空,所以直接将30作为根节点,并将根节点颜色染成黑色即可。
一、插入40,如下图所示。由于此时并没有破坏红黑树的性质,所以此时插入成功。
图1:插入40
二、插入50,插入节点50后,50及其父节点都为红色,由于此时40没有兄弟节点。此时将父节点变黑,祖父节点变红,以祖父节点为根进行左旋。调整后如图2-2所示
图2-1:插入节点50 图2-2:插入节点50调整后
三、插入20,插入节点20后,其父节点30及叔节点50都是红色节点。解决方法是将父节点及叔节点变黑,祖父节点变红。祖父节点变成新的当前节点重新进行算法。由于40是根节点,所以将根节点变黑。调整后如图3-2所示,此时满足红黑树的全部性质。
图3-1:插入节点20 图3-2:插入节点20调整后
四、插入节点35,如图4所示,由于此时并未破坏红黑树的任何性质。不需要进行调整算法。
图4:插入节点35
五、插入节点10,此时10的父节点20及其叔节点35都是红色。将20及35节点变黑,祖父节点30变红。30作为新的当前节点。由于30的父节点40是黑色。所以调整算法结束。调整后如图5-2所示
图5-1:插入节点10
图5-2:插入节点10调整后
六、插入节点11,如图6-1所示,由于11的父节点10是红色节点,并且11是10的右子节点。所以首先以10节点为根节点进行左旋,旋转后其的红黑树如图6-2所示。此时节点10的父节点11为红色,并且10是11节点的左子节点;此时将父节点11变黑,祖父节点20变红,以祖父节点20为根节点进行右旋。旋转后如图6-3所示。
图6-1:插入节点11
图6-2:10为根节点进行左旋后
图6-3:插入节点调整后的红黑树
#include <iostream>
#include <queue>
using namespace std;
static int _rb_black_node = 0;
static int _rb_red_node = 1;
template<typename T>
struct RBNode
{
RBNode():left(NULL),right(NULL),parent(NULL),val(T()),color(_rb_red_node){}
RBNode(const T &v1):left(NULL),right(NULL),parent(NULL),val(v1),color(_rb_red_node){}
RBNode *left;
RBNode *right;
RBNode *parent;
int color;
T val;
};
template<typename T>
class RBTree
{
public:
RBTree():root(NULL){}
~RBTree()
{
if(root)
{
Destroy(root);
}
}
void print();
void Search(const T &v1, RBNode<T> *&node);
bool InsertUnique(const T &v1);
void DeleteValue(const T &v1);
void Destroy(RBNode<T> *p);
void InsertReBalance(RBNode<T> *node);
RBNode<T>* _rbtree_rotate_left(RBNode<T> *node);
RBNode<T>* _rbtree_rotate_right(RBNode<T> *node);
private:
RBNode<T> *root;
};
/*
*
* 打印红黑树的节点信息
*
*/
template<typename T>
void RBTree<T>::print()
{
RBNode<T> *p;
queue<RBNode<T> *> Q;
Q.push(root);
while(!Q.empty())
{
p = Q.front();
Q.pop();
cout<<"节点: "<<p->val<<" ";
if(p->left)
{
cout<<"left:"<<p->left->val<<"->color:"<<p->left->color<<" ";
Q.push(p->left);
}
if(p->right)
{
cout<<"right:"<<p->right->val<<"->color:"<<p->right->color<<" ";
Q.push(p->right);
}
cout<<endl<<endl;
}
}
/*
*
* 搜索v1在红黑树中出现的位置,如果v1在红黑树中则node节点为
* 值为v1所在红黑树中的节点。
* 否则node节点为如果将v1插入到红黑树中的父节点
*
*/
template<typename T>
void RBTree<T>::Search(const T &v1,RBNode<T> *&node)
{
RBNode<T> *p = root;
node = NULL;
while(p)
{
if(p->val == v1)
{
node = p;
break;
}
else if(p->val < v1)
{
node = p;
p = p->right;
}
else
{
node = p;
p = p->left;
}
}
}
template<typename T>
bool RBTree<T>::InsertUnique(const T &v1)
{
RBNode<T> *parent = NULL;
RBNode<T> *newNode = new RBNode<T>(v1);
Search(v1, parent);
if(parent == NULL)
{//红黑树为空,当前插入的节点为根节点。插入后将根颜色变为黑
root = newNode;
root->color = _rb_black_node;
return true;
}
if(parent->val == v1)//v1已经存在红黑树中。不再插入
return false;
if(v1 < parent->val)
{
parent->left = newNode;
}
else
{
parent->right = newNode;
}
newNode->parent = parent;
InsertReBalance(newNode);
return true;
}
/*
*
* 插入节点后进行调整,
* 使所有节点满足红黑树的性质
*
*/
template<typename T>
void RBTree<T>::InsertReBalance(RBNode<T> *node)
{
RBNode<T> *parent = node->parent;
RBNode<T> *grandParent = NULL;
while(parent && parent->color==_rb_red_node)
{
grandParent = parent->parent;
if(parent == grandParent->left)
{//父节点为祖父节点的左儿子
RBNode<T> *uncle = grandParent->right;
if(uncle && uncle->color == _rb_red_node)
{//情形1 父节点与叔节点都为红
//解决方法父与叔变黑,祖父变黑。祖父变为新的当前节点重新进入算法
parent->color = _rb_black_node;
uncle->color = _rb_black_node;
grandParent->color = _rb_red_node;
node = grandParent;
parent = grandParent->parent;
}
else
{
if(node == parent->right)
{//情形2,叔为黑,当前节点为其父节点的右子节点
//解决方法:以父节点为根进行左旋
//操作后将转换为情形3
node = _rbtree_rotate_left(parent);
parent = node->parent;
grandParent = parent->parent;
}
//情形3父为红,当前节点为父节点的左子节点
//解决方法:父节点变黑,祖父节点变红,以
//祖父节点为根节点进行右旋
parent->color = _rb_black_node;
grandParent->color = _rb_red_node;
_rbtree_rotate_right(grandParent);
}
}
else
{//父节点为祖父节点的右子节点,情况与上面相同
RBNode<T> *uncle = grandParent->left;
if(uncle && uncle->color == _rb_red_node)
{
uncle->color = _rb_black_node;
parent->color = _rb_black_node;
grandParent->color = _rb_red_node;
node = grandParent;
parent = node->parent;
}
else
{
if(node == parent->left)
{
node = _rbtree_rotate_right(parent);
parent = node->parent;
grandParent = parent->parent;
}
parent->color = _rb_black_node;
grandParent->color = _rb_red_node;
_rbtree_rotate_left(grandParent);
}
}
}
root->color = _rb_black_node;
}
/*
*
* 左旋
*
*/
template<typename T>
RBNode<T> *RBTree<T>::_rbtree_rotate_left(RBNode<T> *x)
{
RBNode<T> *y = x->right;
if(y == NULL)
{
return x;
}
//x的右节点为y的左节点
x->right = y->left;
if(y->left)//如果y的左节点存在,其父节点为y
y->left->parent = x;
if(root == x)
{//x为root,旋转后y为新的root根节点
root = y;
}
else
{
if(x == x->parent->left)
{//如果x为其父节点的左子节点。
//x的父节点的新左子节点为y
x->parent->left = y;
}
else
{
x->parent->right = y;
}
//y的父节点为x的父节点
y->parent = x->parent;
}
//y的左子节点为x
y->left = x;
//x的父节点为y
x->parent = y;
return x;
}
/*
*
* 右旋
* 分析其逻辑与左旋代码类似
*
*/
template<typename T>
RBNode<T>* RBTree<T>::_rbtree_rotate_right(RBNode<T> *x)
{
RBNode<T> *y = x->left;
if(y == NULL)
{
return x;
}
x->left = y->right;
if(y->right)
y->right->parent = x;
if(root == x)
{
root = y;
}
else
{
if(x == x->parent->left)
{
x->parent->left = y;
}
else
{
x->parent->right = y;
}
y->parent = x->parent;
}
y->right = x;
x->parent = y;
return x;
}
/*
*
* 销毁整棵红黑树
*
*/
template<typename T>
void RBTree<T>::Destroy(RBNode<T> *p)
{
if(p->left)
{
Destroy(p->left);
}
if(p->right)
{
Destroy(p->right);
}
delete p;
}