<红黑树并无咱们想象的那么难> 上、下两篇已经完成, 但愿能帮助到你们.html
根据上一节的红黑树分析, 结合 sgi stl map 的实现, 看看红黑树的源码是如何实现的. 如下主要以代码的注释为主. sgi stl map 底层实现是 _Rb_tree类, 为了方便管理, _Rb_tree 内置了 _M_header, 用于记录红黑树中的根节点, 最小节点和最大节点. 在插入删除中都会对其进行维护. 找到一副美艳的图片: node
我只会展开插入和删除的代码. _Rb_tree 有 insert_unique() 和 insert_equal() 两种, 前者不容许有重复值, 后者能够. insert_unique() 判断是否有重复值的方法利用了二叉搜索树的性质. 细节请参看下面的代码.linux
红黑树是一种类平衡树, 但它不是高度的平衡树, 但平衡的效果已经很好了. 补充说明另外一种 AVL 树, 我以前的博文: 《编程珠玑,字字珠玑》读书笔记完结篇——AVL树算法
用过 STL map 么, 你用过 linux 么(这个说大了), 他们都有红黑树的应用. 当你对搜索的效率要求较高,而且数据常常改动的情景,你能够用红黑树, 也就是 map.编程
至于, 为何不用 AVL 树做为底层实现, 那是由于 AVL 树是高度平衡的树, 而每一次对树的修改, 都要 rebalance, 这里的开销会比红黑树大. 红黑树插入只要两次旋转, 删除至多三次旋转. 但不能否认的是, AVL 树搜索的效率是很是稳定的. 选取红黑树, 我认为是一种折中的方案.函数
// sgi stl _Rb_tree 插入算法 insert_equal() 实现. // 策略概述: insert_equal() 在红黑树找到本身的位置, // 而后交由 _M_insert() 来处理接下来的工做. // _M_insert() 会将节点插入红黑树中, 接着调整红黑树, // 维持性质. template <class _Key, class _Value, class _KeyOfValue, class _Compare, class _Alloc> typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc> ::insert_equal(const _Value& __v) { // 在红黑树中有头结点和根节点的概念, 头结点位于根节点之上, // 头结点只为管理而存在, 根节点是真正存储数据的地方. 头结点和根节点互为父节点, // 是一种实现的技巧. _Link_type __y = _M_header; // 指向头结点 _Link_type __x = _M_root(); // _M_header->_M_parent, 即指向根节点 // 寻找插入的位置 while (__x != 0) { __y = __x; // 小于当前节点要走左边, 大于等于当前节点走右边 __x = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)) ? _S_left(__x) : _S_right(__x); } // __x 为须要插入的节点的位置, __y 为其父节点 return _M_insert(__x, __y, __v); } // sgi stl _Rb_tree 插入算法 insert_unique() 实现. // 策略概述: insert_unique() 一样也在红黑树中找到本身的位置; 咱们知道, // 若是小于等于当前节点会往右走, 因此遇到一个相同键值的节点后, 会往右走一步, // 接下来一直往左走, 因此下面的实现会对往左走的状况作特殊的处理. template <class _Key, class _Value, class _KeyOfValue, class _Compare, class _Alloc> pair<typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator, bool> _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc> ::insert_unique(const _Value& __v) { _Link_type __y = _M_header; // 指向头结点 _Link_type __x = _M_root(); // 指向根节点, 可能为空 bool __comp = true; // 寻找插入的位置 while (__x != 0) { __y = __x; __comp = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)); // 小于当前节点要走左边, 大于等于当前节点走右边 __x = __comp ? _S_left(__x) : _S_right(__x); } iterator __j = iterator(__y); // 在 __y 上创建迭代器 // 我认为下面判断树中是否有存在键值的状况有点绕, // 它充分利用了二叉搜索树的性质, 如此作很 hack, 但不易理解. // 要特别注意往左边插入的状况. // HACKS: // 下面的 if 语句是比 __x 小走左边的状况: 会发现, 若是插入一个已存在的键的话, // __y 最终会定位到已存在键的右子树的最左子树. // 譬如, 红黑树中已经存在一个键为 100 的节点, 其右孩子节点为 101, // 此时若是再插入键为 100 的节点, 由于 100<=100, 因此会往右走到达 101 节点, // 有 100<101, 继而往左走, 会一直往左走.你们稍微画一个例子就能理解. if (__comp) // 特殊状况, 若是 __j 指向了最左孩子, 那么确定要插入新节点. if (__j == begin()) return pair<iterator,bool>(_M_insert(__x, __y, __v), true); // 其余状况, 这个时候也是往左边插入, 若是存在重复的键值, // 那么 --__j 能定位到此重复的键的节点. else --__j; // HACKS: 这里比较的是 __j 和 __v, 若是存在键值, 那么 __j == __v, // 会跳过 if 语句. 不然执行插入. 也就是说若是存在重复的键, 那么 __j // 的值确定是等于 __v if (_M_key_compare(_S_key(__j._M_node), _KeyOfValue()(__v))) return pair<iterator,bool>(_M_insert(__x, __y, __v), true); // 此时 __y.value = __v, 不容许插入, 返回键值所在位置 return pair<iterator,bool>(__j, false); } // _M_insert() 是真正执行插入的地方. // 策略概述: 插入策略已经在上篇中详述, 能够根据上篇文章的描述, // 和下面代码的注释, 加深对红黑树插入算法里理解 template <class _Key, class _Value, class _KeyOfValue, class _Compare, class _Alloc> typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc> ::_M_insert(_Base_ptr __x_, _Base_ptr __y_, const _Value& __v) { _Link_type __x = (_Link_type) __x_; // 新节点插入的位置. // 关于 __x 的疑问: // 1. 它被放到下面的, 第一个 if 语句中, 我以为是没有必要的, // 由于从调用 _M_insert() 的函数来看, __x 老是为空. // 2. 既然 __x 是新节点插入的位置, 那么为何不直接在 __x 上建立节点, // 还要在下面经过比较来决定新节点是左孩子仍是右孩子; // 不如直接用指针的指针或者指针的引用来完成, 省去了下面的判断. _Link_type __y = (_Link_type) __y_; // 新节点的父节点 _Link_type __z; // 新节点的位置 if (__y == _M_header || __x != 0 || _M_key_compare(_KeyOfValue()(__v), _S_key(__y))) { // 新节点应该为左孩子 __z = _M_create_node(__v); _S_left(__y) = __z; // also makes _M_leftmost() = __z // when __y == _M_header if (__y == _M_header) { _M_root() = __z; _M_rightmost() = __z; } else if (__y == _M_leftmost()) _M_leftmost() = __z; // maintain _M_leftmost() pointing to min node } // 新节点应该为右孩子 else { __z = _M_create_node(__v); _S_right(__y) = __z; if (__y == _M_rightmost()) _M_rightmost() = __z; // maintain _M_rightmost() pointing to max node } _S_parent(__z) = __y; _S_left(__z) = 0; _S_right(__z) = 0; // 从新调整 _Rb_tree_rebalance(__z, _M_header->_M_parent); // 更新红黑树节点数 ++_M_node_count; // 返回迭代器类型 return iterator(__z); } // 插入新节点后, 可能会破坏红黑树性质, _Rb_tree_rebalance() 负责维持性质. // 其中: // __x 新插入的节点 // __root 根节点 // 策略概述: 红黑树插入从新调整的策略已经在上篇中讲述, // 能够结合上篇文章和这里的代码注释, // 理解红黑树的插入算法. inline void _Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root) { // 将新插入的节点染成红色 __x->_M_color = _S_rb_tree_red; while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) { // __x 的父节点也是红色的状况. 提示: 若是是黑色节点, 不会破坏红黑树性质. if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) { // 叔父节点 _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right; if (__y && __y->_M_color == _S_rb_tree_red) { // 第 1 种状况, N,P,U 都红(G 确定黑). // 策略: G->红, N,P->黑. 此时, G 红, 若是 G 的父亲也是红, 性质又被破坏了, // HACK: 能够将 GPUN 当作一个新的红色 N 节点, 如此递归调整下去; // 特俗的, 若是碰巧将根节点染成了红色, 能够在算法的最后强制 root->红. __x->_M_parent->_M_color = _S_rb_tree_black; __y->_M_color = _S_rb_tree_black; __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red; __x = __x->_M_parent->_M_parent; } else { if (__x == __x->_M_parent->_M_right) { // 第 2 种状况, P 为红, N 为 P 右孩子, U 为黑或缺乏. // 策略: 旋转变换, 从而进入下一种状况: __x = __x->_M_parent; _Rb_tree_rotate_left(__x, __root); } // 第 3 种状况, 可能由第二种变化而来, 但不是必定: P 为红, N 为红. // 策略: 旋转, 交换 P,G 颜色, 调整后, 由于 P 为黑色, 因此不怕 // P 的父节点是红色的状况. over __x->_M_parent->_M_color = _S_rb_tree_black; __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red; _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root); } } else { // 下面的代码是镜像得出的, 脑补吧. _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left; if (__y && __y->_M_color == _S_rb_tree_red) { __x->_M_parent->_M_color = _S_rb_tree_black; __y->_M_color = _S_rb_tree_black; __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red; __x = __x->_M_parent->_M_parent; } else { if (__x == __x->_M_parent->_M_left) { __x = __x->_M_parent; _Rb_tree_rotate_right(__x, __root); } __x->_M_parent->_M_color = _S_rb_tree_black; __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red; _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root); } } } __root->_M_color = _S_rb_tree_black; } // 删除算法, 直接调用底层的删除实现 _Rb_tree_rebalance_for_erase(). template <class _Key, class _Value, class _KeyOfValue, class _Compare, class _Alloc> inline void _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc> ::erase(iterator __position) { _Link_type __y = (_Link_type) _Rb_tree_rebalance_for_erase(__position._M_node, _M_header->_M_parent, _M_header->_M_left, _M_header->_M_right); destroy_node(__y); --_M_node_count; } // 删除节点底层实现, 删除可能会破坏红黑树性质, // _Rb_tree_rebalance() // 负责维持性质. 其中: // __z 须要删除的节点 // __root 根节点 // __leftmost 红黑树内部数据, 即最左子树 // __rightmost 红黑树内部数据, 即最右子树 // 策略概述: _Rb_tree_rebalance_for_erase() 会根据 // 删除节点的位置在红黑树中找到顶替删除节点的节点, // 即无非是删除节点左子树的最大节点或右子树中的最小节点, // 此处用的是有一种策略. 接着, 会调整红黑树以维持性质. // 调整的算法已经在上篇文章中详述, 能够根据上篇文章的描述 // 和此篇的代码注释, 加深对红黑树删除算法的理解. inline _Rb_tree_node_base* _Rb_tree_rebalance_for_erase(_Rb_tree_node_base* __z, _Rb_tree_node_base*& __root, _Rb_tree_node_base*& __leftmost, _Rb_tree_node_base*& __rightmost) { // __z 是要删除的节点 // __y 最终会指向要删除的节点 _Rb_tree_node_base* __y = __z; // N 节点 _Rb_tree_node_base* __x = 0; // 记录 N 节点的父节点 _Rb_tree_node_base* __x_parent = 0; // 只有一个孩子或者没有孩子的状况 if (__y->_M_left == 0) // __z has at most one non-null child. y == z. __x = __y->_M_right; // __x might be null. else if (__y->_M_right == 0) // __z has exactly one non-null child. y == z. __x = __y->_M_left; // __x is not null. // 有两个非空孩子 else { // __z has two non-null children. Set __y to __y = __y->_M_right; // __z's successor. __x might be null. // __y 取右孩子中的最小节点, __x 记录他的右孩子(可能存在右孩子) while (__y->_M_left != 0) __y = __y->_M_left; __x = __y->_M_right; } // __y != __z 说明有两个非空孩子的状况, // 此时的删除策略就和文中提到的普通二叉搜索树删除策略同样: // __y 记录了 __z 右子树中最小的节点 // __x 记录了 __y 的右孩子 // 用 __y 顶替 __z 的位置, __x 顶替 __y 的位置, 最后用 __y 指向 __z, // 从而 __y 指向了要删除的节点 if (__y != __z) { // relink y in place of z. y is z's successor // 将 __z 的记录转移至 __y 节点 __z->_M_left->_M_parent = __y; __y->_M_left = __z->_M_left; // 若是 __y 不是 __z 的右孩子, __z->_M_right 有左孩子 if (__y != __z->_M_right) { __x_parent = __y->_M_parent; // 若是 __y 有右孩子 __x, 必须有那个 __x 替换 __y 的位置 if (__x) // 替换 __y 的位置 __x->_M_parent = __y->_M_parent; __y->_M_parent->_M_left = __x; // __y must be a child of _M_left __y->_M_right = __z->_M_right; __z->_M_right->_M_parent = __y; } // __y == __z->_M_right else __x_parent = __y; // 若是 __z 是根节点 if (__root == __z) __root = __y; // __z 是左孩子 else if (__z->_M_parent->_M_left == __z) __z->_M_parent->_M_left = __y; // __z 是右孩子 else __z->_M_parent->_M_right = __y; __y->_M_parent = __z->_M_parent; // 交换须要删除节点 __z 和 替换节点 __y 的颜色 __STD::swap(__y->_M_color, __z->_M_color); __y = __z; // __y now points to node to be actually deleted } // __y != __z 说明至多一个孩子 else { // __y == __z __x_parent = __y->_M_parent; if (__x) __x->_M_parent = __y->_M_parent; // 将 __z 的父亲指向 __x if (__root == __z) __root = __x; else if (__z->_M_parent->_M_left == __z) __z->_M_parent->_M_left = __x; else __z->_M_parent->_M_right = __x; // __leftmost 和 __rightmost 是红黑树的内部数据, 由于 __z 多是 // __leftmost 或者 __rightmost, 所以须要更新. if (__leftmost == __z) if (__z->_M_right == 0) // __z->_M_left must be null also // __z 左右孩子都为空, 没有孩子 __leftmost = __z->_M_parent; // makes __leftmost == _M_header if __z == __root else __leftmost = _Rb_tree_node_base::_S_minimum(__x); if (__rightmost == __z) if (__z->_M_left == 0) // __z->_M_right must be null also __rightmost = __z->_M_parent; // makes __rightmost == _M_header if __z == __root else // __x == __z->_M_left __rightmost = _Rb_tree_node_base::_S_maximum(__x); // __y 一样已经指向要删除的节点 } // __y 指向要删除的节点 // __x 即为 N 节点 // __x_parent 指向 __x 的父亲, 即 N 节点的父亲 if (__y->_M_color != _S_rb_tree_red) { // __y 的颜色为黑色的时候, 会破坏红黑树性质 while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black)) // __x 不为红色, 即为空或者为黑. 提示: 若是 __x 是红色, 直接将 __x 替换成黑色 if (__x == __x_parent->_M_left) { // 若是 __x 是左孩子 _Rb_tree_node_base* __w = __x_parent->_M_right; // 兄弟节点 if (__w->_M_color == _S_rb_tree_red) { //第 2 状况, S 红, 根据红黑树性质P,SL,SR 必定黑. // 策略: 旋转, 交换 P,S 颜色. __w->_M_color = _S_rb_tree_black; __x_parent->_M_color = _S_rb_tree_red; // 交换颜色 _Rb_tree_rotate_left(__x_parent, __root); // 旋转 __w = __x_parent->_M_right; // 调整关系 } if ((__w->_M_left == 0 || __w->_M_left->_M_color == _S_rb_tree_black) && (__w->_M_right == 0 || __w->_M_right->_M_color == _S_rb_tree_black)) { // 提示: 这是 第 1 状况和第 2.1 状况的合并, 由于处理的过程是同样的. // 但他们的状况仍是要分门别类的. 已经在文章中详细支出, // 彷佛大多数的博文中没有提到这一点. // 第 1 状况, N,P,S,SR,SL 都黑. // 策略: S->红. 经过 PN,PS 的黑色节点数量相同了, 但会比其余路径多一个, // 解决的方法是在 P 上从状况 0 开始继续调整. // 为何要这样呢? HACKS: 由于既然 PN,PS // 路径上的黑节点数量相同并且比其余路径会少一个黑节点, // 那何不将其总体当作了一个 N 节点! 这是递归原理. // 第 2.1 状况, S,SL,SR 都黑. // 策略: P->黑. S->红, 由于经过 N 的路径多了一个黑节点, // 经过 S 的黑节点个数不变, 因此维持了性质 5. over // 可能你们会有疑问, 不对啊, 2.1 的状况, // 策略是交换父节点和兄弟节点的颜色, 此时怎么没有对父节点的颜色赋值呢? // HACKS: 这就是合并状况的好处, 由于就算此时父节点是红色, // 并且也将兄弟节点颜色改成红色, 你也能够将 PS,PN 当作一个红色的 N 节点, // 这样在下一个循环当中, 这个 N 节点也会变成黑色. 由于此函数最后有一句话: // if (__x) __x->_M_color = _S_rb_tree_black; // 合并状况, 节省代码量 // 固然是能够分开写的 // 兄弟节点染成黑色 __w->_M_color = _S_rb_tree_red; // 调整关系 __x = __x_parent; __x_parent = __x_parent->_M_parent; } else { if (__w->_M_right == 0 || __w->_M_right->_M_color == _S_rb_tree_black) { // 第 2.2.1 状况, S,SR 黑, SL 红. // 策略: 旋转, 变换 SL,S 颜色. if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black; __w->_M_color = _S_rb_tree_red; _Rb_tree_rotate_right(__w, __root); // 调整关系 __w = __x_parent->_M_right; } // 第 2.2.2 状况, S 黑, SR 红. // 策略: 旋转, 交换 S,P 颜色, SR->黑色, 从新得到平衡. __w->_M_color = __x_parent->_M_color; __x_parent->_M_color = _S_rb_tree_black; if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black; _Rb_tree_rotate_left(__x_parent, __root); break; } // 下面的代码是镜像得出的, 脑补吧. } else { // same as above, with _M_right <-> _M_left. _Rb_tree_node_base* __w = __x_parent->_M_left; if (__w->_M_color == _S_rb_tree_red) { __w->_M_color = _S_rb_tree_black; __x_parent->_M_color = _S_rb_tree_red; _Rb_tree_rotate_right(__x_parent, __root); __w = __x_parent->_M_left; } if ((__w->_M_right == 0 || __w->_M_right->_M_color == _S_rb_tree_black) && (__w->_M_left == 0 || __w->_M_left->_M_color == _S_rb_tree_black)) { __w->_M_color = _S_rb_tree_red; __x = __x_parent; __x_parent = __x_parent->_M_parent; } else { if (__w->_M_left == 0 || __w->_M_left->_M_color == _S_rb_tree_black) { if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black; __w->_M_color = _S_rb_tree_red; _Rb_tree_rotate_left(__w, __root); __w = __x_parent->_M_left; } __w->_M_color = __x_parent->_M_color; __x_parent->_M_color = _S_rb_tree_black; if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black; _Rb_tree_rotate_right(__x_parent, __root); break; } } if (__x) __x->_M_color = _S_rb_tree_black; } return __y; }
捣乱 2013-9-29lua