一、简介
为了解决线性探构造哈希表所出现的大量哈希冲突,我们采用了另外一种方法,便是开链法。而在SGI版本的STL中调用的也正是哈希桶的实现方法。
注意:
开链法实现哈希表的时候会出现很多很多的错误,比如各种模板错误,此时需要耐心,慢慢调整。同时又为了扩展hash_map与hash_set更改了一些模板参数,所以经过调试错误的过程,绝对会对模板有一个全新的认识。
二、拉链法实现原理:
采用了由N个头指针构成的数组,在每个表格内部维护一个List,经过哈希函数的计算,我们可以得到某一个List,我们所做的插入删除和查找等操作全部位于这个LIst上。
我们需要注意的是虽然List的遍历是线性的,但是如果List足够短,我们操作起来速度还是很快。
三、拉链法优缺点
优点:
- 解决了线性探测所导致的太多的哈希冲突。
- 删除结点相比于开放定址法更容易实现(在线性探测中如果删除结点,后面的结点无法访问)。
- 搜索的时间下降
缺点:
- 如果相同元素过多,元素在一个桶内部链接过长,反而导致时间复杂度上升。解决思路是桶中元素不再指向链表,而指向一个红黑树。
四、拉链法实现
哈希表里面存放一个链表的指针,和一个判断的数据。可以把哈希表中的元素看作链表的第一个结点,类似于指针数组。
链表里面包含指向下一个结点的指针,数据tata,结点构造如下:
template <class ValueType>
struct HashNode
{
ValueType _valueField;
HashNode* _next;
HashNode(const ValueType& valueField)
:_valueField(valueField)
, _next(NULL)
{}
};
hashtable数据结构
插入
第一步:检查容量是否需要扩容。
第二步:查看此时的key所对应的哈希桶位置。
第三步:构造出新的结点。
第四步:找到此哈希桶位置所链接的List所对应的结点,如果存在则插入失败,不存在则插入,此时采用头插法(头插只需要考虑结点为空和不为空,但是可以直接处理)。
查找
第一步:找到此key所对应的哈希桶的位置。
第二步:进入此哈希桶所指向的List查看Key是否相同即可。
删除
类似链表删除,定义两个指针,指向前后两个元素。
第一步:找到key所对应的哈希桶的位置。
第二步:定义两个指针,一个是需要删除的指针,一个是它前一个指针。
判断两种情况
- 情况一:要删除结点为链表的头结点
- 情况二:删除结点为其他结点。
第三步:分以上两个结点的情况删除即可。
负载因子判别
size如果等于——table的size,增容时也可以选择重新哈希结点来构造新的哈希表,但是这样做时间复杂度很高,所以我们采用现代的写法。
现代写法是:
- 创建新表,resize获取容量