1.hashtable
二叉搜索树具有对数平均时间的表现,但这样的表现构造在一个假设上:输入数据有足够的随机性。而hashtable在插入、删除、搜寻等操作上也具有“常数平均时间的表现”,而且这种表现是以统计为基础的,不依赖于输入的随机性。
一个简单的hashtable的例子:
如果元素是32bits而不是16bits,我们要准备的array就必须是4GB的。这就大的不切实际了。如何避免一个大的荒谬的数组呢?办法之一就是使用某种映射函数,将大数映射为小数。负责将某以元素映射为一个“大小可接受之索引”,这样的函数称为hash function(散列函数)。使用散列函数会导致多个元素映射到同一个位置,我们可以采用线性探测、二次探测、开链等做法,解决冲突。
STL的hash table采用的是开链的做法。即在每个表格单元中维护一个list,然后我们在那个list身上执行元素的插入、搜寻、删除等操作。
下图为STL中的hash table的图形表述:
(1)hashtable的迭代器
hashtable的迭代器是forward_iterator类型,它只有前进操作,没有后退操作。当前进时,它尝试着从当前节点出发,前进一个位置。如果节点被安置在list内,利用节点的next指针即可轻易达成前进操作。如果目前节点正在list的尾端,就调到下一个bucket上,指向下一个list的头节点。
(2)hashtable的数据结构
虽然开链发并不要求表格大小必须为质数,但STL仍然以质数来设计表格大小,并且先将28个质数计算好,以备随时访问,同时提供一个函数,用来查询这28个质数之中,最接近给定数并大于给定数的质数。
(3)hashtable的构造与内存管理
当我们构造一个拥有若干个节点的hash table时,会调用如下构造函数:
当进行插入操作时,会首先判断需不需要扩充表格。当hashtable里面存储的元素个数大于bucket vector的size时,就表明需要扩充bucket vector,扩充时会扩充到下一个更大的质数。
在不需要重建bucket vector的情况下插入新节点,需要调用insert_unique_noresize()函数。
如果允许插入重复键值节点的话,则使用insert_equal()函数。
如何判断元素落在哪个bucket上?这本来是hash function的责任,STL把这个任务包装了一层,交给了bkt_num()函数。这么做的原因是很多元素型别不能直接拿来对hashtable的大小进行模运算,这时我们需要做一些转换(比如char*)。
hash()函数针对char,int,long等整数型别基本上不做处理,直接返回原值,但对于字符串类型,则会调用一个转换函数。
由此可知,STL的hashtable无法处理上述所列类别之外的类别,例如string,double,float。欲处理这些类别,用户需要为它们定义hash function。
2.hash_set
set是以RB-tree为底层实现机制。STL除set之外还提供了另外一种容器:hash_set,它提供的接口与set完全一样,只是它的是以hashtable为底层实现机制。需要特别注意的是,hashtable有一些无法处理的型别(string,double,float等,除非用户自己定义hash函数),hash_set同样无法处理。
运用set,为的是能够根据键值快速的搜索元素。这一点,不管底层是RB-tree或是hashtable,都能达成任务。但如果底层是RB-tree,则set有自动排序的功能。而如果底层是hashtable,则不能实现自动排序。
3.hash_map
hash_map是以RB-tree为底层实现的另一种map。运用map,为的是能够根据键值快速的搜索元素。这一点,不管底层是RB-tree或是hashtable,都能达成任务。但如果底层是RB-tree,则map有自动排序的功能。而如果底层是hashtable,则不能实现自动排序。
4.hash_multiset
实现与hash_set基本一致,唯一的区别是set里允许有重复的元素,即往set里面插值的时候调用的是hashtable的insert_equal()而不是insert_unique()。
5.hash_multimap
实现与hash_map基本一致,唯一的区别是map里允许有重复的元素,即往map里面插值的时候调用的是hashtable的insert_equal()而不是insert_unique()。
原文:http://blog.csdn.net/yao_wust/article/details/44237167