最近觉得C++生疏了,拿出侯捷的《STL源码剖析》翻了翻,看到C++ set,map底层实现机制,其中采用的就是红黑树数据结构,另外Linux内核对内存管理和进程调度都用到了红黑树,看来它不能让人小视。自己从网上和书上重新看了下红黑树,把个人的理解放到博客上,跟大家讨论,也作为自己的重新梳理的方式。
红黑树(Red-Black Tree)
它是在1972 年由Rudolf Bayer 发明的,他称之为"对称二叉B 树",它现代的名字是在Leo J. Guibas 和Robert Sedgewick 于1978 年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
红黑树本身是二叉搜索树,同时它应该始终满足五个性质
红黑树的难点在于插入和删除操作涉及到的红黑树重新调整问题,关于原理性问题,有篇文章《红黑树原理详解》,作者:余强. 讲的比较清楚,也可以参照《CLRS》第13章红黑树的描述,以下主要结合c语言实现代码加注释的方式进行分析,编完代码后进行了一定的测试,如果还存在问题,可回帖反馈,我会进行更改,谢谢。
/*
* key:新插入节点键值,root:红黑树根节点
* 红黑树节点插入
* 1、插入新节点
* 2、旋转红黑树并做颜色调整
*/
rb_node_t *rb_tree_insert(int key, rb_node_t* root)
{
rb_node_t *last_node;
rb_node_t *curnode;
/* 创建节点 */
rb_node_t *node = (rb_node_t *)malloc(sizeof(rb_node_t));
node->key = key;
curnode = root; //temp node,just for save something
/* 树为空 */
if (NULL == root)
{
node->color = black;
node->left_child = NULL;
node->right_child = NULL;
node->parent = NULL;
return node;
}
/* 向下搜索,直到找到相应的位置可以插入 */
while (curnode)
{
last_node = curnode;
node->key > curnode->key ? (curnode = curnode->right_child) : (curnode = curnode->left_child);
}
/* 判断插入搜索到的节点的左孩子还是右孩子 */
if (node->key > last_node->key)
last_node->right_child = node;
else
last_node->left_child = node;
node->parent = last_node; //回马枪设置父节点
node->left_child = NULL;
node->right_child = NULL;
node->color = red;
/* 重新使红黑树调整为平衡状态 */
root = rb_tree_rebalance(node, root);
return root;
}
兄弟节点颜色为红色
/* * 2011-8-11 * 华为 * 红黑树性质:在满足二叉搜索树的基础上 * 1、根节点为黑色 * 2、从根节点到叶节点的黑色节点总数相等 * 3、不能有相邻的两个红色节点 * 4、每个节点颜色非黑即红 * 5、每个叶节点(空节点null)都是黑色 */ #include<stdio.h> #include<stdlib.h> #include"RBtree.h" /* 红黑树左旋操作,旋转点:base */ /* * A * / \ * (base) B C * / \ * D E * \ * F(new) */ static rb_node_t *rb_tree_rotate_left(rb_node_t *base, rb_node_t *root) { rb_node_t *right = base->right_child; base->right_child = right->left_child; // E的左子树转换为B的右子树 if (right->left_child != NULL) right->left_child->parent = base; // 回马枪设置父节点 right->parent = base->parent; // E的父节点替换为B的父节点 if (base == root) root = right; else if (base->parent->left_child == base) base->parent->left_child = right; else base->parent->right_child = right; right->left_child = base; // 设置旋转后的E节点的左孩子为B节点 base->parent = right; // 回马枪设置 return root; } /* 红黑树右旋操作,旋转点base * * A * / \ * B C (base) * / \ * D E * / * F(new) */ static rb_node_t *rb_tree_rotate_right(rb_node_t *base, rb_node_t *root) { rb_node_t *left = base->left_child; base->left_child = left->right_child; // D节点的右子树转换为C节点的左子树 if (left->right_child) left->right_child->parent = base; // 回马枪设置父节点 left->parent = base->parent; // D的父节点替换为C的父节点 if (base == root) root = left; else if (base->parent->right_child == base) base->parent->right_child = left; else base->parent->left_child = left; left->right_child = base; // 设置旋转后的D节点的右孩子为E节点 base->parent = left; return root; } /* * 重新调整红黑树状态 * 使红黑树转换回平衡状态 * 使用rebalance函数说明红黑树至少有3层,深度至少为2 * * A * / \ * B C * / * D */ rb_node_t* rb_tree_rebalance(rb_node_t* base, rb_node_t*root) { /* * 父节点为黑或当前节点为根节点,则无需调整红黑树结构,立即返回 */ if (base == root || base->parent->color == black) // 注意顺序不可颠倒,因为当前节点非根节点,所以必然存在父节点 { root->color = black; // 根节点必然为黑色 return root; } /* * 情况1:父节点为红且非根节点(存在祖父节点),叔叔节点为黑色 */ //确定uncle节点 rb_node_t *uncle; if (base->parent->parent->left_child == base->parent) uncle = base->parent->parent->right_child; else uncle = base->parent->parent->left_child; if (uncle == NULL || uncle->color == black)//叔叔节点为黑色 { if (base->parent->parent->left_child == base->parent) //父节点为祖父节点的左节点 { if (base->parent->left_child == base) //左外侧插入 { //对父节点和祖父节点(旋转基点)进行右旋转,并更改它们的颜色 base->parent->color = black; base->parent->parent->color = red; root = rb_tree_rotate_right(base->parent->parent, root); } else //左内侧插入 { //对插入节点和父节点进行左旋转,并更改插入节点和祖父节点的颜色 base->color = black; base->parent->parent->color = red; root = rb_tree_rotate_left(base->parent, root); root = rb_tree_rotate_right(base->parent, root); //再对插入节点和旋转后父节点进行右旋转 } } else if(base->parent->parent->right_child == base->parent) //父节点为祖父节点的右节点 { if (base->parent->right_child == base) //右外侧插入 { //对父节点和祖父节点进行左旋转,并更改它们的颜色 base->parent->color = black; base->parent->parent->color = red; root = rb_tree_rotate_left(base->parent->parent, root); } else { //右内侧插入,对插入节点和父节点进行右旋转,并更改插入节点和祖父节点的颜色, //再对插入节点和祖父节点进行左旋转 base->color = black; base->parent->parent->color = red; root = rb_tree_rotate_right(base->parent, root); root = rb_tree_rotate_left(base->parent, root); } } } else //情况2:父节点为红,叔叔节点为红色 { //更改父节点和叔叔节点颜色为黑色,祖父节点颜色为红色,并继续向上进行平衡调整 base->parent->color = black; uncle->color = black; base->parent->parent->color = red; root = rb_tree_rebalance(base->parent->parent, root); } return root; } /* * 红黑树节点插入 * 1、插入新节点 * 2、旋转红黑树并做颜色调整 */ rb_node_t *rb_tree_insert(int key, rb_node_t* root) { /* 创建节点 */ rb_node_t *node = (rb_node_t *)malloc(sizeof(rb_node_t)); node->key = key; rb_node_t *curnode = root; //temp node,just for save something rb_node_t *last_node; /* 树为空 */ if (NULL == root) { node->color = black; node->left_child = NULL; node->right_child = NULL; node->parent = NULL; return node; } /* 向下搜索,直到找到相应的位置可以插入 */ while (curnode) { last_node = curnode; node->key > curnode->key ? (curnode = curnode->right_child) : (curnode = curnode->left_child); } /* 判断插入搜索到的节点的左孩子还是右孩子 */ if (node->key > last_node->key) last_node->right_child = node; else last_node->left_child = node; node->parent = last_node; //回马枪设置父节点 node->left_child = NULL; node->right_child = NULL; node->color = red; /* 重新使红黑树调整为平衡状态 */ root = rb_tree_rebalance(node, root); return root; } /* * 红黑树节点删除: * 根据二叉查找树删除性质: * 被删除的节点是被删除节点n的中序遍历的前驱节点(左子树中数值最大的节点)(补:同样可以是右子树中最小的节点) * ---->根据上述思路,被删除的节点必然只有一个左孩子或无孩子 * ---->根据上述推理结合红黑树性质--->1、被删除的必定是只有一个红色孩子或没有孩子的结点; * 2、根据上述推理所确定的被删除的节点如果是红色节点,则必然是叶子节点 */ static rb_node_t *search_rbtree_node(int key, rb_node_t *root) { if (root == NULL) return root; rb_node_t *tmp = root; while (tmp) { if (tmp->key == key) //找出对应节点 return tmp; if (tmp->key > key) // 当前节点key值大于搜索的key值 tmp = tmp->left_child; else tmp = tmp->right_child; } return tmp; //搜索不到指定节点,返回NULL } /****************************************************************************** 函数名 : rb_tree_erase_fixed 功能描述 : 红黑树删除节点后重新进行调整以满足性质 输入参数 : node :顶替删除位置的新的节点 parent :新节点的父节点 root :红黑树根节点 输出参数 : 无 返回值 : 新的红黑树根节点 其他说明 : 作者日期 : 王一静 2011-8-30 修改履历 : ******************************************************************************/ rb_node_t *rb_tree_erase_fixed(rb_node_t *node, rb_node_t* parent, rb_node_t *root) { rb_node_t *other; /* 新节点的兄弟节点 */ /* * 新节点为黑色,且非根节点,则说明被删除节点为黑色 * 即红黑树一边子树的高度被减1,需要重新调整平衡 */ while ( (!node || black == node->color) && node != root) { if (parent->left_child == node) { other = parent->right_child; /* * case1:兄弟节点为红色节点,则父节点必然为黑色 */ if (red == other->color) { other->color = black; parent->color = red; root = rb_tree_rotate_left(parent, root); /* 旋转后红黑树左右已经平衡,但需要查看新父节点左右子树是否满足红黑树性质 */ other = parent->right_child; } /* * case2:兄弟节点为黑色节点,左右侄子节点均为黑色 */ if ((!other->left_child || black == other->left_child->color) &&(!other->right_child || black == other->right_child->color)) { /* * 兄弟节点为黑色,则改变兄弟节点颜色为红色,即达到父节点左右子树平衡 * 若此时父节点为红色,则改变父节点颜色为黑色即可弥补被删除的节点的黑色高度,调整即可结束 * 若此时父节点为黑色,则该父节点及子树高度整体减了1,向上递归解决该问题 */ other->color = red; node = node->parent; parent = node->parent; } else { /* * case3:黑色兄弟节点有一个孩子节点颜色为红色 */ if (!other->left_child || black == other->left_child->color) { /* 内侧为黑色,外侧孩子为红色 */ other->color = parent->color; parent->color = black; other->right_child->color = black; root = rb_tree_rotate_left(parent, root); } else { /* 内侧孩子为红色 */ other->left_child->color = parent->color; parent->color = black; root = rb_tree_rotate_right(other, root); root = rb_tree_rotate_left(parent, root); } node = root; break; } } else { other = parent->left_child; /* * case1:兄弟节点为红色节点,则父节点必然为黑色 */ if (red == other->color) { other->color = black; parent->color = red; root = rb_tree_rotate_right(parent, root); /* 旋转后红黑树左右已经平衡,但需要查看新父节点左右子树是否满足红黑树性质 */ other = parent->left_child; } /* * case2:兄弟节点为黑色节点,左右侄子节点均为黑色 */ if ((!other->left_child || black == other->left_child->color) &&(!other->right_child || black == other->right_child->color)) { /* * 兄弟节点为黑色,则改变兄弟节点颜色为红色,即达到父节点左右子树平衡 * 若此时父节点为红色,则改变父节点颜色为黑色即可弥补被删除的节点的黑色高度,调整即可结束 * 若此时父节点为黑色,则该父节点及子树高度整体减了1,向上递归解决该问题 */ other->color = red; node = node->parent; parent = node->parent; } else { /* * case3:黑色兄弟节点有一个孩子节点颜色为红色 */ if (!other->right_child || black == other->right_child->color) { /* 内侧为黑色,外侧孩子为红色 */ other->color = parent->color; parent->color = black; other->left_child->color = black; root = rb_tree_rotate_right(parent, root); } else { /* 内侧孩子为红色 */ other->right_child->color = parent->color; parent->color = black; root = rb_tree_rotate_left(other, root); root = rb_tree_rotate_right(parent, root); } node = root; break; } } } if (node) node->color = black; return root; } rb_node_t *rb_tree_erase(int key, rb_node_t *root) { rb_node_t *child, *parent; //real delete节点右孩子和父节点 /* 根据键值查询红黑树中对应节点 */ rb_node_t *node = NULL; node = search_rbtree_node(key, root); /* 搜索不到指定key值的节点 */ if (node == NULL) return root; if (!node->left_child) //delete节点无左子树 child = node->right_child; else if (!node->right_child) //delete节点无右子树 child = node->left_child; else { rb_node_t *old = node; node = node->right_child; while (node->left_child) //在右子树中查找最小的节点(即最左边的节点) { node = node->left_child; } if (old->parent) //old节点是否使根节点 { if(old->parent->left_child == old) old->parent->left_child = node; else old->parent->right_child = node; } else root = node; child = node->right_child; //真正需要被移除节点的右孩子 parent = node->parent; if (parent == old) /* node与old节点相邻 */ parent = node; else { if (child) child->parent = parent; parent->left_child = child; node->right_child = old->right_child; old->right_child->parent = node; } node->color = old->color; node->left_child = old->left_child; old->left_child->parent = node; return rb_tree_erase_fixed(child, parent, root); } parent = node->parent; if (child) { child->parent = node->parent; } if (parent) { if (parent->left_child == node) parent->left_child = child; else parent->right_child = child; } else root = child; if (node->color == black) return rb_tree_erase_fixed(child, parent, root); return root; }