之前分析二叉搜索树和平衡二叉树时,真心感觉树的实现真是难,特别是平衡二叉树,不平衡之后需要调整,还要考虑各种情况,累感不爱.今天看到这个红黑树,发现比平衡二叉树还难,但是红黑树比平衡二叉树使用的场景更多,所以平常使用时,我们需要了解红黑树的实现原理,如果有能力,可以自己实现,但是如果实在做不出来,也没关系,因为STL和linux内核都有现成的红黑树实现,拿来用即可,前提是了解红黑树原理.
红黑树原理
红黑树和平衡二叉树一样,本质上都是二叉搜索树,但是二者的搜索效率都能实现平均效率logn,比二叉搜索树性能好.平衡二叉树实现原理是判断每个节点的左右子树的高度差是否等于2,如果等于2,则要通过旋转来实现树的左右子树高度差平衡.而红黑树实现原理是节点的颜色和旋转来实现的,实现较复杂,先看下红黑树的满足条件:
- 每个结点要么是红的,要么是黑的。
- 根结点是黑的。
- 每个叶结点,即空结点(NIL)是黑的。
- 如果一个结点是红的,那么它的俩个儿子都是黑的。
- 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。 如果某棵二叉搜索树满足上述条件,则为红黑树.为什么满足上述条件可以实现平衡了?主要是第4条和第5条,由第5条可以得出,某个到所有叶子节点最长路径是最短路径的两倍.即一条路径为一黑一红和一条路径为全黑,大体上红黑树是平衡的,只是没有AVL树要求那么严格.
我不推荐直接看STL源码剖析这本书中的红黑树,而是先到网上博客先看看大家是怎么写,因为这本书STL实现的红黑树比较复杂,不好看懂.我推荐博客michael,因为这篇博客一步一步讲解很好,图做的也很好理解.
红黑树节点和迭代器
红黑树节点和迭代器的设计和slist原理一样,将结构和数据分离.原理如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct __rb_tree_node_base { typedef __rb_tree_color_type color_type; typedef __rb_tree_node_base* base_ptr; color_type color; // 红黑树的颜色 base_ptr parent; // 父节点 base_ptr left; // 指左节点 base_ptr right; // 指向右节点 } template <class Value> struct __rb_tree_node : public __rb_tree_node_base { typedef __rb_tree_node<Value>* link_type; Value value_field; // 存储数据 }; |
红黑树的基础迭代器为struct __rb_tree_base_iterator,主要成员就是一个__rb_tree_node_base节点,指向树中某个节点,作为迭代器与树的连接关系,还有两个方法,用于将当前迭代器指向前一个节点decrement()和下一个节点increment().下面看下正式迭代器的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
template <class Value, class Ref, class Ptr> struct __rb_tree_iterator : public __rb_tree_base_iterator { typedef Value value_type; typedef Ref reference; typedef Ptr pointer; typedef __rb_tree_iterator<Value, Value&, Value*> iterator; typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator; typedef __rb_tree_iterator<Value, Ref, Ptr> self; typedef __rb_tree_node<Value>* link_type; __rb_tree_iterator() {}//迭代器默认构造函数 __rb_tree_iterator(link_type x) { node = x; }//由一个节点来初始化迭代器 __rb_tree_iterator(const iterator& it) { node = it.node; }//迭代器复制构造函数 //迭代器解引用,即返回这个节点存储的数值 reference operator*() const { return link_type(node)->value_field; } #ifndef __SGI_STL_NO_ARROW_OPERATOR //返回这个节点数值值域的指针 pointer operator->() const { return &(operator*()); } #endif /* __SGI_STL_NO_ARROW_OPERATOR */ //迭代器++运算 self& operator++() { increment(); return *this; } self operator++(int) { self tmp = *this; increment(); return tmp; } //迭代器--运算 self& operator--() { decrement(); return *this; } self operator--(int) { self tmp = *this; decrement(); return tmp; } }; inline bool operator==(const __rb_tree_base_iterator& x, const __rb_tree_base_iterator& y) { return x.node == y.node; // 两个迭代器相等,指这两个迭代器指向的节点相等 } inline bool operator!=(const __rb_tree_base_iterator& x, const __rb_tree_base_iterator& y) { return x.node != y.node; // 两个节点不相等,指这两个迭代器指向的节点不等 } |
迭代器的解引用运算,返回的是这个节点的值域.所以对于set来说,返回的就是set存储的值,对于map来说,返回的就是pair<key,value>键值对.
红黑树
为了实现红黑树的插入和删除平衡,STL树实现了几个旋转以及平衡函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
inline void __rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root) inline void __rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root) inline void __rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root) 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) |
这几个函数是实现红黑树平衡的重要操作,左旋转,右旋转,插入一个节点之后的平衡操作,删除一个节点的平衡操作.这几个函数较为复杂,我就不分析了,但是我觉得看网上的博客会比较好理解.
下面看下rb_tree真正的定义.先是创建节点和销毁节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//分配节点存储空间 link_type get_node() { return rb_tree_node_allocator::allocate(); } //回收一个节点的空间 void put_node(link_type p) { rb_tree_node_allocator::deallocate(p); } //创建一个节点 link_type create_node(const value_type& x) { link_type tmp = get_node(); // 配置空间 __STL_TRY { construct(&tmp->value_field, x); // 建造内容 } __STL_UNWIND(put_node(tmp)); return tmp; } link_type clone_node(link_type x) { // 复制一个节点(主要是是颜色) link_type tmp = create_node(x->value_field); tmp->color = x->color; tmp->left = 0; tmp->right = 0; return tmp; } void destroy_node(link_type p) { destroy(&p->value_field); // 解析内容 put_node(p); // 回收节点空间 } |
红黑树的成员主要有3个,
1 2 3 |
size_type node_count; // 记录树的大小(节点的个数) link_type header; Compare key_compare; // 节点间的比较器,应该是个函数对象 |
对于header,其实相当与链表中的头节点,不存储数据,可用于红黑树的入口.header的设计可以说的STL红黑树设计的一个亮点,header和root互为父节点,header的左节点指向最小的值,header的右节点指向最大的值,所以header也可以作为end()迭代器指向的值.图示如下:;
接下来就是一些小函数,都是实现获取节点成员变量的函数,有一个需要提下:
1 |
static const Key& key(link_type x) { return KeyOfValue()(value(x)); } |
KeyOfValue这是个函数对象,对于set和map的实现是不一样的,主要功能就是从一个节点中获取这个节点存储的键值,对于set而言,这个函数对象为 identity<value_type>,这个函数对象返回的就是这个set存储的值,而对于map而言,这个函数对象为 select1st<value_type>,map的值域为pair对象,所以select1st就是获取这个pair的第一个成员.
接下就对重要函数做个简单的介绍,因为太复杂了,而且这篇文章主要是讲解set和map.
-
对于set和multiset,map和multimap而言,最大的区别就是是否允许键值重复,而反应在红黑树上,则为插入函数的不同.set和map用的是insert_unique,而multiset和multimap用的是insert_equal函数.具体源码,我也是一知半解,所以就不分析了,误人子弟就不好了.
-
有插入就有删除函数,红黑树提供的是erase函数,但是使用这个函数之后,可能导致红黑树不满足那5个条件,所以要调用 __rb_tree_rebalance_for_erase来维持树的平衡.
-
对于multiset和multimap而言,红黑树还提供了查询与某个键值相等的节点迭代器范围,
1
pair<iterator,iterator> equal_range(const key_type& x);
这个函数返回二叉树中和键值x相等的迭代器范围.而set和map都是不允许键值重复的,所以就不要用这个函数,直接用find函数即可.
- 红黑树还提供了查询不小于和大于某个键值的函数:
1
2
3
4
5
6
//返回不小于k的第一个节点迭代器
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::lower_bound(const Key& k)
//返回大于k的第一个节点迭代器
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::upper_bound(const Key& k)
map实现
map底层用的是红黑树,所以map只是在红黑树上加了一层封装,map中用的用的红黑树定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
typedef Key key_type; //键值类型 typedef T data_type; // 值类型 typedef T mapped_type; //值类型 typedef pair<const Key, T> value_type; // 值类型,map存储pair键值对 typedef Compare key_compare; // 键值函数对象 class value_compare//值域比较器,其实也是比较键值 : public binary_function<value_type, value_type, bool> { friend class map<Key, T, Compare, Alloc>; protected : Compare comp; value_compare(Compare c) : comp(c) {} public: bool operator()(const value_type& x, const value_type& y) const { return comp(x.first, y.first); } }; //map中红黑树的定义 typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type; rep_type t; |
可以看到在map中,传入红黑树的KeyOfValue函数对象是select1st<value_type>,而value_type为pair类型,这个函数对象就是获取pair第一个键值,为了键值比较排序.
map构造函数
map构造函数有分为默认构造函数和带比较函数对象的构造的构造函数,一个是用默认的比较对象less<key>,另一个是可以自己定义比较对象的构造函数
1 2 |
map() : t(Compare()) {} explicit map(const Compare& comp) : t(comp) {} |
map还支持用一对输入迭代器来初始化,迭代器之间即为要插入map的数据,所以可以将vector首尾迭代器作为参数传入map构造函数来初始化
1 2 3 4 5 6 7 |
template <class InputIterator> map(InputIterator first, InputIterator last) : t(Compare()) { t.insert_unique(first, last); } template <class InputIterator> map(InputIterator first, InputIterator last, const Compare& comp) : t(comp) { t.insert_unique(first, last); } |
map还支持值类型的数组指针范围来初始化map,例如pair<string,int> parr[10],然后用这个数组的首尾指针来初始化map.
1 2 3 4 |
map(const value_type* first, const value_type* last) : t(Compare()) { t.insert_unique(first, last); } map(const value_type* first, const value_type* last, const Compare& comp) : t(comp) { t.insert_unique(first, last); } |
map插入操作
map插入分为单个值插入,在某个节点插入以及插入一对迭代器范围内的元素.
1 2 3 4 5 6 7 8 9 10 |
pair<iterator,bool> insert(const value_type& x) { return t.insert_unique(x); } iterator insert(iterator position, const value_type& x) { return t.insert_unique(position, x); } void insert(const value_type* first, const value_type* last) { t.insert_unique(first, last); } void insert(const_iterator first, const_iterator last) { t.insert_unique(first, last); } |
对于第一个迭代器,返回的是一个pair<iterator,bool>,迭代器指向插入节点的位置,布尔值为插入是否成功.其他都是简单的调用红黑树的方法.
map删除操作
map删除操作提供删除某个迭代器指向的节点,某个键指向的节点,以及一对迭代器指向的元素:
1 2 3 |
void erase(iterator position) { t.erase(position); } size_type erase(const key_type& x) { return t.erase(x); } void erase(iterator first, iterator last) { t.erase(first, last); } |
map查找函数
map查找函数有查找某个键值的find函数,查找某个键值个数的count函数,两个限界函数,最后查找范围的函数
1 2 3 4 5 6 7 8 9 10 11 |
//查找某个键值对应的节点迭代器 iterator find(const key_type& x) { return t.find(x); } //查找某个键值的个数 size_type count(const key_type& x) const { return t.count(x); } //查找不小于,即大于等于键值x的节点迭代器, iterator lower_bound(const key_type& x) {return t.lower_bound(x); } //查找大于键值x的节点迭代器 iterator upper_bound(const key_type& x) {return t.upper_bound(x); } //返回lower_bound和upper_bound迭代器对. pair<iterator,iterator> equal_range(const key_type& x) { return t.equal_range(x); |
map一定要提到的函数是重载下标注运算符,这个函数也算是查找某个键所对应的值,但是如果某个键不存在的话,则会插入pair对,键值为k,值为data_type类型的默认值.
1 2 3 |
T& operator[](const key_type& k) { return (*((insert(value_type(k, T()))).first)).second; } |
这个函数有点复杂,insert(value_type(k, T()))先返回pair<iterator,bool>第一个iterator,即这个键所对应节点的迭代器,然后取这个键所对应的值pair<key_type,value_type>,最后取出第二个元素即可.
所以要查找某个元素不能用这个下标表示法,应该用find函数,因为前者会将不存在的键插入到map中.
set的实现
先来看下set中红黑树的定义:
1 2 3 4 5 6 7 8 |
typedef Key key_type; typedef Key value_type; // 键值和值域比较器是一样的,因为set存储的就是一个值,而不是键值对 typedef Compare key_compare; typedef Compare value_compare; typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type; rep_type t; |
这里面最重要的就是identity<value_type>函数对象了.这个就是获取set存储的value的键值部分.也是标准模板库的一部分.
看到这我才发现,set的接口和map的接口几乎一模一样...
转载:http://luodw.cc/2015/11/19/STL-map/