红黑树
红黑树是一种自平衡二叉搜索树,之前学习过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的空节点
- 五个模板参数。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实现