STL分析(六 关联式容器)

红黑树

红黑树是一种自平衡二叉搜索树,之前学习过AVL树,但为什么红黑树在实际工程中应用这么广泛后面还是要了解一下。
红黑树有几个需要遵守的规则:比如节点有红黑两种颜色(1)根节点必须是黑节点(2)红节点不能相邻(3)从任意节点到叶节点的所有路径上的黑节点数目是固定的。
当插入一个新节点时,上述规则会被违反,所以需要对树做一些调整,调整大概分两种:重新涂色和旋转,旋转和AVL树一样分四种情况。
在这里插入图片描述

  • 每个容器都会维护一个begin()函数,在这里指向最左边的节点,end()函数指向最右边节点
  • 按正常的++ite遍历,便可以得到排序状态
  • rb_tree不应该改变元素值,否则会破坏他的容器排列结构,但编程方面没有阻止,因为rb_tree为map和set服务,而map只允许元素data被改变,key是无法改变的
  • insert_unique()不能插入重复的key,insert_equal()可以插入重复的Key
template <class Key,
          class Value,//value是key+data
          class KeyOfValue,//在value里面key怎么拿到
          class Compare,//两个元素比大小要用key来比较,一个function object
          class Alloc = alloc>
class rb_tree{
protected:
    typedef __rb_tree_node<Value> rb_tree_node;
    ......
public:
    typedef rb_tree_node* link_type;
    ......
protected:
    size_type node_count;    //rb_tree的大小
    link_type header;
    Compare key_compare;     //key的大小比较准则
};//大小为12,本来大小应该是4+4+1=9,functor object大小为0,编译器将其大小调整为1。在没有对齐情况下,内存以4字节对齐分配。

这里的header是可以放进去的,方便操作。里面没有元素,类似于list的空节点

  1. 五个模板参数。Key就是Key,节点的大小关系是按Key排的;Value是整个node,而不是key对应的data,比如对于map来说,Value就是个pair;KeyOfValue代表如何从Value中提取出Key,也就是一个可调用对象的类型,传入一个Value,返回对应的Key;Compare就是比较Key的可调用对象的类型;分配器就不多说了

以下展示了当真正使用rb_tree时怎么调用,但一般不这么用,会使用更上层的东西。
在这里插入图片描述
其中identity和less都用functor object形式调用。

template <class T>
struct identity : public unary_function<T, T>{
	const T& operator()(const T& x) const { return x;}
};

2. 三个成员。node_count表示节点的个数,key_compare就是个可调用对象,重点说一下header。header就是一个指向rb_tree_node的指针,rb_tree_node表示红黑树的节点类型,里面包含一个value对象,以及指向parent,left和right的指针。而这个header指向的节点是一个额外节点,它的存在完全是为了方便程序实现,里面没有成员,它的parent指向红黑树真正的根节点(根节点的parent也指向它),另外两个指针指向的是begin()和end()。整个红黑树的结构,以及一个node的结构,如下图所示:

在这里插入图片描述
3. 两个重要的成员函数。insert_unique()和insert_equal(),分别代表了插入的元素的key是唯一的,以及允许key重复的元素插入。明显分别对应了set,map及其multi版本的插入操作。
**4. 大小。**一个rb_tree求sizeof结果为12,因为4+4+1=9,然后四字节对其所以结果为12。

G4.9版本的rb_tree

在一个class里面,有一个指针,或者有一个单一的东西,来表现它的实现手法,至于主体本身,并没有太多设计,真正的实作设计是在impl的部分,这种设计叫做handle and body。
在这里插入图片描述
对G4.9的红黑树求sizeof结果为24,三个指针加一个color,color是个枚举类型,没搞清color的大小,老师也没讲,一个枚举类型竟然占12字节?

set和multiset

  • set/multiset的value和key合一,value就是key
  • set/multiset使用rb_tree提供遍历的操作,正常++ite遍历,便能得到排序状态
  • 无法使用set/multiset的iterators改变元素值,因为key就是value,有严谨的数据排列。set/multiset的iterator是其底部的RB tree的const-iterator,就是为了禁止user对元素赋值
  • set元素的key必须独一无二,其insert()用的是rb_tree的insert_unique(),multiset元素的key可以重复,因此其insert()用的是rb_tree的insert_equal()
    在这里插入图片描述
    set底层实现是红黑树,由此set也可以看成是一种container adapter。

VC6中不提供identity,其map和set如何使用rb_tree

在这里插入图片描述
在VC6.0中提供_Kfn类来获得key of value

map和multimap

template <class Key,
          class T,
          class Compare = less<Key>,
          class Alloc = alloc>
class map{
public:
    typedef Key key_type;
    typedef T data_type;
    typedef T mapped_type;
    typedef pair<const Key, T> value_type;//在这里传入的key为const key,以此限制不能修改key的值
    typedef Compare key_compare;
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;
......
};
 
template<class Pair>
struct select1st:
    public unary_function<Pair, typename Pair::first_type>{
        const typename Pair::first_type&
        operator()(const Pair& x) const
        { return x.first; }
};
 
template<class Arg, class Result>
struct unary_function{
    typedef Arg argument_type;
    typedef Result result_type;
};

map和multimap都不能通过itertator改变key的值,这是通过将pair的第一个参数设置为const实现的,但在map/multimap中的迭代器是红黑树的普通迭代器,可以通过它修改value的值。

map用[ ]进行插入操作

mapped_type&
    operator[](const key_type& __k)
    {
        iterator __i = lower_bound(__k);    //返回第一个不小于__k的元素的位置,即不破坏元素次序的第一个位置
 
        if(__i == end() || key_compare()(__k, (*__i).first))//如果找不到_k的位置,则返回end()位置
            __i = insert(__i, value_type(__k, mapped_type()));
 
        return (*__i).second;
    }

注意这里返回的是引用,所以可以用类似mp[i] = val的方式插入元素。

hashtable

在这里插入图片描述
由于空间不够,只能把元素的按编号取余放到空间内,如5和105编号的元素会发生碰撞。因此要设计碰撞处理函数(一次,二次方程式),但由于处理时间过多,最终选用链表串在一起,即链地址法。
在这里插入图片描述
看起来很理想,因为链表可以无限长,但链表很长时间搜寻速度很慢,因为链表只能循序查找。因此当链表过长时要重新打散(散列表)。当元素个数大于篮子个数时,进行rehashing,打散后篮子数量变为两倍,并选择附近的素数,这里为97。在这里插入图片描述
查看hashtable源码,看到buckets采用类似deque的控制中心一样含有一个vector记录篮子,其他的是三个function object,占0字节,但编译器会把他们转化为1字节,然后还有一个记录元素个数的size_type类型,一般为unsigned integer,总大小为12+4+1+1=1=19,对齐为20字节。

  • hashtable的节点_hashtable_node类似链表,含有一个指向下一个Node的next指针和自身的value值
  • hashtable迭代器设计为一个cur指针指向node,一个hashtable指针用于在vector中移动到下一个篮子
  • hashtable的几个模板参数:Value指表中放的实际元素,对set来说是Key,对map来说是pair;Key不用解释;HashFcn是哈希函数;ExtractKey是指如何从Value中提取出Key;EqualKey顾名思义,代表比较Key相等的可调用对象的类型;Alloc是分配器。
  • hash函数在varaidc中提过,是一个泛化的空模板类,有很多特化版本

hash function的设计在这里插入图片描述

在这里插入图片描述

  • 数值本身可以当成编号
  • 如果是字符串,每个字母ascii码*5然后相加
  • 标准库中没有提供C++风格字符串的hash< std::string>偏特化版本
    在这里插入图片描述
    在hashtable中的取余运算最终通过hash的function object实现

unordered容器

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值