5. 关联式容器(1)

红黑树简介

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_nodecreate_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_treeinsert_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_treeerase函数实现:

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和键值keypair中的第一元素被视为键值,第二元素被视为实值;
  • 两个元素不允许有相同的键值key;

pairmap的数据结构如下:

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;
};

mapiterator的定义与set不相同,因为其允许用户通过迭代器修改元素的datamap中不允许修正元素的键值,但是可以修改元素的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-treeinsert_equal()而非insert_unique()

multimap

multimap的特性以及用法与map完全相同,唯一的区别在于它允许键值重复,因此它的插入采用的是底层机制RB-treeinsert_equal()而非insert_unique()

总结

本文,先介绍了平衡二叉树RB-tree的特性,并对RB-tree的操作进行简单分析,然后对setmap基于RB-tree的实现的具体内容分别介绍,最后说明了multisetmultimapsetmap在使用的区别。

猜你喜欢

转载自blog.csdn.net/hello_dear_you/article/details/128677820