STL源码剖析学习十:红黑树的实现
RB-Tree的节点设计:
节点设计分为两层:
struct __rb_tree_node_base { typedef __rb_tree_color_type color_type; typedef __rb_tree_node_base* base_ptr; color_typr color; base_ptr parent, left, right; static base_ptr minimun(base_ptr x) { while (x->left != 0) x = x->left; return x; } static base_ptr maximun(base_ptr x) { while (x->right != 0) x = x->right; return x; } };
template<class value> struct __rb_tree_node:public __rb_tree_node_base { typedef __rb_tree_node<value>* link_type; value value_field;//关键是把节点的值独立出来 };
RB-Tree的迭代器:
也分两层,和slist的设计比较相似,比较特殊的是前进和后退的操作。
struct __rb_tree_base_iterator { typedef __rb_tree_node_base::base_ptr base_ptr; typedef bidirectional_iterator_tag iterator_catogory; typedef ptrdiff_t difference_type; base_ptr node; void increment() { if(node->right != 0) { node = node->right; while(node->left != 0) node = node->left; } //如果有右子结点,就找到其右子结点的最左子节点 else { base_ptr y = node->parent; //如果没有右子结点,找出父节点,如果现行节点的父节点也是右子结点,就继续上溯 //直到现行节点不是右子结点为止 while(node == y->right) { node = y; y = y->parent; } //若此时右子结点不等于此时的父节点,此时的父节点即为解答(为了应付特殊情况:当前节点为根节点) //否则此时的node为解答 if(node->right != y) node = y; } } void decrement() { if(node->color == __rb_tree_red && node->parent->parent == node) node == node->right; //如果本节点为红且父节点的父节点为自己,说明该节点为header,前一节点为该节点的右子结点 //header的右子结点为该树的最右子结点,指向max节点 else if (node->left != 0) { base_ptr y = node->left; while(y->right != 0) y = y->right; node = y; } //如果有左子节点,就取左子节点在最右子结点 else { //找出父节点,如果本节点也为左子节点,则继续上溯直到本节点不为左子节点 base_ptr y = node->parent; while(node == y->left) { node = y; y = y->parent; } node = y;//此时的父节点即为所求 } } }
正规迭代器:
继承了基础迭代器
template <class value, class ref, class ptr> struct __rb_tree_iterator:public __rb_tree_base_iterator
RB-tree 的元素操作
insert_equal()//该函数允许有重复值插入 { 从根节点开始向下寻找适当的插入点 如果遇到大则往左,遇到小则往右 __insert(x, y, v)//x为新插入点,y为插入点的父节点,v为值 }
insert_unique() { while { 查找插入点: 从根节点开始向下寻找适当的插入点 如果遇到大则往左,遇到小与等于则往右 } 查找完成后y指向插入点的父节点(此时其必为叶节点) 如果离开循环时遇到大,则将其插于左侧(默认大的放左边,小的放右边) 如果插入点的父节点为最左节点,则直接插入 return __insert(x, y, v) 否则看父节点的前驱结点的键是否和要插入的键相同 (如果要出现相同的键,只可能是该节点的直接前驱) 如果不相等 则插入 return __insert(x, y, v) 到此则说明有相等值,则不插入 }
真正的插入程序
__insert() { 根据插入点和插入点的父节点 调节指针,并且判断是否为最右或者最左节点,调整leftmost和rightmost的指向 调整插入节点的指针的指向 __rb_tree_rebalance(z, header->parent)//一为新增节点,二为root ++node_count; return iterator(z); //返回新增节点的迭代器 }
__rb_tree_rebalance()
根据之前所说红黑树插入后调整树形的规则进行调整
里面用到两个全局函数__rb_tree_rotate_left/right 左旋/右旋调整
set、map、multimap、multiset
有了都是在红黑树的基础上实现的,功能和接口都比较简单
set的特性是,所有元素都会根据键值自动排序,不允许有键值和实值,两者等价,不允许有两个相同的键值
set不能通过迭代器改变元素的值,因为涉及到排列规则,要改变值只能先删除后插入
set<T>::iterator被定义为底层的const_iterator杜绝写入操作
set拥有某些与list相同的性质:当对元素进行插入或者删除操作时,之前的迭代器都不会失效。
map和set的区别是map区分键值和实值
multimap和multiset的特点是允许有相同键值的存在
因为其底层调用为insertt_equal而非insert_unique
STL源码剖析这块内容里没有涉及到红黑树删除操作的实现
下面看了算法导论和其他一些资料后补上:
红黑树的删除类似于二叉查找树的删除,实际上删除的节点是该节点的直接前驱。
并且把该节点直接前驱的值赋给要删除的节点的位置。
分情况讨论:
1.如果该节点是红色节点,直接染黑。
2.如果该节点为黑色节点,且为根节点,直接删除
3.如果该节点为黑且兄弟w为红,则p必为黑,把p和w改色,然后单旋转解决
4.兄弟为黑且兄弟2儿子都为黑:
把当前节点和兄弟节点中都抽出一层黑色来给父节点,把父节点当做当前节点,继续进入算法。
5.当前为黑,兄弟节点为黑,兄弟左子节点为红,右子结点为黑:
把兄弟染红,把左子节点染黑,以兄弟节点为支点旋转,重新进入算法(转换为下一种情况)
6.兄弟为黑,兄弟右子结点为红,兄弟左子节点任意:
把兄弟节点染成父节点的颜色,把父节点染成黑色,兄弟节点的右子结点染黑,当前节点的父节点为支点旋转即可
(图就不画了,懒╮(╯▽╰)╭)
来源:https://www.cnblogs.com/w0w0/archive/2012/04/24/2468303.html