您的位置:首页 > 其它

STL源码剖析学习十一:hashtable

2012-04-25 15:19 225 查看
二叉搜索树具有对数时间的搜索复杂度,但是这样的复杂度是再输入数据有足够的随机性的假设上
哈希表在插入删除搜索操作上也具有常数时间的表现,而且这种表现是以统计为基础,不需要依赖输入元素的随机性

hashtable提供对任何有名项的存取操作和删除操作,可以视为一种字典结构,

负载系数:元素个数除以表格大小,除非使用开链,负载系数永远在0,1之间

碰撞的解决方法:
线性探测:
当hash function计算出某个元素的插入位置,而在该位置上的空间已经不可用时,循序往下寻找,
如果到达尾端则绕到头部继续寻找,直到找到一个可用的空间为止。
只要表格足够大,肯定能找到一个位置存放数据。
进行元素搜索时,如果计算出来的位置上的元素与我们搜索的目标不符,就循序一一往下寻找,直到找出符合者或者遇上空格。
元素的删除必须使用惰性删除,就是只标记删除记号而不是真正删除,当哈希表重新整理时再实际删除。
带来的问题:可能存在主集团,有一大团用过的数据,操作很可能在主集团中反复搜索,不断解决碰撞问题,最后找到合适的位置,但是又助长了主集团的范围。

二次探测:主要用来解决主集团的问题
如果计算得的位置H已经被占用,就尝试H+i*i。

开链:在每个表格中维护一个list,hash function计算得到一个位置,然后再那个list上进行删除查找插入的操作。
此时表格的负载系数将大于1。
STL中的哈希表就是用这种做法。

hashtable的桶与节点:
表格内的每个单元,涵盖的不只是节点(元素),还可以是一个桶节点。

hash table的节点定义:
template <class value>
struct __hashtable_node
{
__hashtable_node* next;
value val;
}


buckets的聚合体,是以vector完成,以便有动态扩充能力。

hashtable的迭代器:
迭代器只有++没有--操作,也没有逆向迭代器。

operator++()
{
检查当前节点的下一个节点
如果存在就是它
如果不存在则寻找下一个bucket
取下一个bucket的第一个元素
}


hashtable的数据结构
template<class value, 节点的实值类别
class key, 节点的键值类别
class HashFcn, hash function函数类别
class ExtractKey, 从节点中取出键值的方法
class EqualKey, 判断键值相同与否的方法
class Alloc> 空间配置器,默认使用std::alloc

hashtable的插入与表格重整:

insert_unique(const value_type& obj)//不允许元素重复的插入
{
resize(num_elements+1);//判断是否需要重整表格
return insert_unique_noresize(obj);
}


判断是否需要重建,如果不需要则返回,如果需要则重建

resize()
{
判断表格是否需要重建:拿元素个数和bucket vector的大小来比,如果前者比后者大就重整
(因此,每个bucket(list)的大小和bucket vector的大小相同
如果要重建,则找出下一个质数作为vector的大小,建立新的buckets
处理每一个旧的bucket{
建立一个新的节点指向节点所指的串行的起始节点
处理每一个旧bucket所含串行的每一个节点{
找出节点落在哪一个新的bucket内
令旧bucket指向其所指的串行的下一个节点
将当前节点插入到新的bucket内,成为其串行的第一个节点
回到旧bucket所指的待处理串行,准备处理下一个节点
}
}
新旧两个buckets对调,如果双方大小不同,大的会变小,小的会变大
离开时释放temp的内存
}


不需要重建的情况下插入

insert_unique_noresize()
{
计算出obj应该位于哪个bucket
令first指向bucket对应的串行的头部
如果bucket已经被占用,检查整个链表
如果发现链表中有相同的元素,就立即返回
产生新节点,令新节点为链表的第一个节点,节点个数加一
}


如果允许相同元素存在,则调用

insert_equal_noresize()
{
计算obj应该位于哪个bucket
令first指向bucket对应的串行的头部
如果bucket已经被占用,检查整个链表
如果发现链表中有相同元素,则产生新节点,插入目前节点之后,节点数加一
返回一个迭代器,指向新节点

进行到这里说明没有发现新节点
产生新节点
将新节点插入到链表的头部,节点个数加一
返回一个迭代器,指向新节点
}


查找

find()
{
计算obj应该位于哪个bucket
从头部开始,一一对比每个元素的键值,比对成功就返回
}


统计

count()
{
计算obj应该位于哪个bucket
从bucket list的头开始,一一对比每个元素的键值,对比成功就加一
}


hash function
计算元素的位置,SGI将这项任务先给了bkt_num(),然后再由它调用hash function,取得一个可以对hashtable进行模运算的值
对于char int long 都只是忠实的返回原值
const char *设计一个转换函数
for(;*s;++s)
h=5*h+*s
而对于string double float都必须由用户来定义hash function

hash_set
由于hash_set所供应的操作接口hashtable都提供了,所以几乎所有的hash_set操作行为,都只是转调用hashtable的操作行为而已
hash_set没有自动排序功能
使用方式和set完全相同

其余的hash_map hash_multimap hash_multiset用法和其对应的非hash的版本相同。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: