红黑树简介
除AVL-tree
之外,RB-tree
是另一个被广泛使用的平衡二叉搜索树,也是STL
中关联式容器的底层实现。
红黑树特性
RB-tree
是一个二叉搜索树,回顾一下二叉搜索树具有如下性质:
- 如果左子树不为空,则左子树所有节点值都小于根节点的值;
- 如果右子树不为空,则右子树所有节点值都大于或等于根节点的值;
- 任意一颗子树也是一颗二叉搜索树;
二叉搜索树的查找时间复杂度为O(logn)
RB-tree
除了符合二叉搜索树的基本特性之外,同时还具备以下特性:
- 每个节点不是红色就是黑色;
- 根节点为黑色;
- 如果节点为红,其子节点必须为黑;
- 任一节点到
NULL
叶子节点的任何路径,所包含黑节点数必须相同;
红黑树的操作
RB-tree
中应该包括节点的查找、插入和删除三个操作。RB-tree
插入和删除操作需要考虑的情况太多,再这里就不多介绍(俺也看不明白呀!)。下面主要介绍插入和查找操作,在STL
中的一些实现细节。
查找节点
RB-tree
本身就是一颗二叉搜索树,满足二叉搜索树的性质:左子树所有节点的值小于根节点,右子树所有节点的值大于等于根节点。查找节点的规则如下:
插入节点
RB-tree
节点插入的情况比较复杂,有七种情况,如下图所示,具体操作不做过多介绍。
RB-tree
提供两种插入操作:insert_unique()
和insert_equal()
,前者函数表示插入的键值key
在整棵树中必须是独一无二的,而当插入相同键值时,插入操作不起作用;后者表示被插入节点的键值key
可以在整棵树中重复。
红黑树的数据结构
下面的RB-tree的定义,其中定义有专属的空间配置器,通过get_node
和create_node
两个函数配置和释放一个节点的内存,clone_node
复制一个节点的值和颜色,destroy_node
释放节点的内容和内存。
template <class Key, class Value, class KeyofValue, class Compare,
class Alloc = alloc>
class rb_tree
{
protected:
typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;
public:
typedef Key key_value;
typedef Value value_type;
...
};
上述template
中五个参数的含义分别如下:
- Key:键值key的数据类型;
- Value:由key和data组织,在map中可以看到是一个pair结构;
- KeyofValue:如何从Value中分离出key来,在map中是通过select1st函数完成;
- Compare:key值的比较函数;
- Alloc:内存配置器
set
set
具有如下特性:
- 所有元素都会根据元素的键值自动被排序;
- 只包含键值,键值
key
就是实值value
; - 不允许两个元素有相同的键值;
set
的数据结构如下:
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
>
class set
{
public:
typedef Key key_type;
typedef Key value_type;
typedef Compare key_compare;
typedef Compare value_compare;
private:
typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;
rep_type t; // 成员变量
public:
// set不允许迭代器执行写入操作, 因此iterator定义为RB-tree的const_iterator
typedef typename rep_type::const_iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
};
set
底层通过调用rb_tree
的数据结构实现各种功能,下述函数可以简单调用rb_tree
实现:
iterator begin() const {
return t.begin(); }
iterator end() const {
return t.end(); }
bool empty() const {
return t.empty(); }
size_type size() const {
return t.size(); }
size_type max_size() const {
return t.max_size(); }
插入操作
set
元素的key
要求必须独一无二,因此,insert()
底层调用rb_tree
的insert_unique()
实现。set中提供如下三种插入操作:
typedef pair<iterator, bool> pair_iterator_bool;
pair<iterator, bool> insert(const value_type& x) // 向RB-tree中插入一个节点, 自动排序
{
pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
return pair<iterator, bool>(p.first, p.second);
}
typedef pair<iterator, bool> pair_iterator_bool;
pair<iterator, bool> insert(iterator position, const value_type& x)
{
// 指定插入的位置, 自动排序后, 插入的节点位置未知
typedef typename rep_type::iterator rep_iterator;
return t.insert_unique((rep_iterator&)position, x);
}
template <class InputIterator>
void insert(InputIterator first, InputInterator last)
{
t.insert_unqiue(first, last);
}
测试demo
,需要注意三个insert
函数的返回值都不一样:
#include <iostream>
#include <set>
int main ()
{
std::set<int> myset;
std::set<int>::iterator it;
std::pair<std::set<int>::iterator,bool> ret;
// set some initial values:
for (int i=1; i<=5; ++i) myset.insert(i*10); // set: 10 20 30 40 50
ret = myset.insert(20); // no new element inserted
if (ret.second==false) it=ret.first; // "it" now points to element 20
std::cout << "current iterator value: " << *it << std::endl;
it = myset.insert (it,25); // max efficiency inserting
std::cout << "current iterator value: " << *it << std::endl;
it = myset.insert (it,24); // max efficiency inserting
std::cout << "current iterator value: " << *it << std::endl;
it = myset.insert (it,26); // no max efficiency inserting
std::cout << "current iterator value: " << *it << std::endl;
int myints[]= {
5,10,15}; // 10 already in set, not inserted
myset.insert (myints,myints+3);
std::cout << "myset contains:";
for (it=myset.begin(); it!=myset.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
删除操作
同样删除操作也提供三种形式,底层通过调用rb_tree
的erase
函数实现:
void erase(iterator position)
{
// 传入迭代器, 删除对应的节点
typedef typename rep_type::iterator rep_iterator;
t.erase((rep_iterator&) position);
}
size_type erase(const key_type& x)
{
return t.erase(x);
}
void erase(iterator first, iterator last)
{
typedef typename rep_type::iterator rep_iterator;
t.erase((rep_iterator&)first, (rep_iterator&)last)
}
void clear() {
t.clear(); }
查找操作
面对关联式容器,应该使用其提供的find
函数查找元素,会比使用STL
算法中的find()
更有效率,因为STL
算法find()
底层只是顺序查找。
iterator find(const key_type& x) const {
return t.find(x); }
size_type count (const key_type& x) const {
return t.count(x); }
map
map
具有如下特性:
- 所有元素都会根据元素的键值key自动排序;
- 节点元素都是
pair
结构,同时拥有实值data
和键值key
,pair
中的第一元素被视为键值,第二元素被视为实值; - 两个元素不允许有相同的键值
key
;
pair
和map
的数据结构如下:
template <class T1, class T2>
struct pair{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
// 构造函数
pair() : first(T1()), second(T2()) {
} // 临时变量
pair (const T1& a, const T2& b) : first(a), second(b) {
}
};
template < class Key, class T, // Key为键值, T为data型别
class Compare = less<key>,
class Alloc = alloc>
class map
{
public:
typedef Key key_type;
typedef T data_type;
typedef pair<const Key, T> vaule_type;
private:
typedef rb_tree<key_type, value_type,
select1st<value_type>, key_compare, Alloc> rep_type;
// 成员变量
rep_type t;
public:
typedef typename rep_type::iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
};
map
中iterator
的定义与set
不相同,因为其允许用户通过迭代器修改元素的data
。map
中不允许修正元素的键值,但是可以修改元素的data
内容,因为map
元素的data
并不影响map
元素的排列规则。map
中的查找、插入和删除操作的实现与set
一样,都是调用rb_tree
实现,这里就不再做介绍。
独特的operator[]
public:
typedef Key key_type;
typedef T data_type;
typedef pair<const Key, T> vaule_type;
T& operator[] (const key_type& k)
{
return (*((insert(value_type(k, T()))).first).second);
}
如果键值k
在容器map
中,则返回键值key
对应的data
,如果键值k不存在容器map
中,则使用键值key
新插入一个元素,返回元素对应的data
。键值k不存在的情况下,无论是否指定对应的data
,容器map
的大小都会加1
。
value_type(k, T())
首先,根据键值和实值做出一个元素,由于实值未知,所以产生一个与实值型别相同的临时对象替代
insert(value_type(k, T()))
将元素插入到map
中
((insert(value_type(k, T()))).first
插入操作返回一个pair
,其第一个元素是map
的迭代器,指向新插入的元素
*((insert(value_type(k, T()))).first).second
对迭代器dereference
,得到一个由键值和实值组成的pair
,取其第二元素,即为实值
multiset
multiset
的特性以及用法与set
完全相同,唯一的区别在于它允许键值重复,因此它的插入采用的是底层机制RB-tree
的insert_equal()
而非insert_unique()
。
multimap
multimap
的特性以及用法与map
完全相同,唯一的区别在于它允许键值重复,因此它的插入采用的是底层机制RB-tree
的insert_equal()
而非insert_unique()
。
总结
本文,先介绍了平衡二叉树RB-tree
的特性,并对RB-tree
的操作进行简单分析,然后对set
和map
基于RB-tree
的实现的具体内容分别介绍,最后说明了multiset
和multimap
与set
和map
在使用的区别。