您的位置:首页 > 其它

STL源码剖析学习十:红黑树的实现

2012-04-24 16:25 218 查看
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.兄弟为黑,兄弟右子结点为红,兄弟左子节点任意:
把兄弟节点染成父节点的颜色,把父节点染成黑色,兄弟节点的右子结点染黑,当前节点的父节点为支点旋转即可

(图就不画了,懒╮(╯▽╰)╭)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: