hashtable(散列表)是一种数据结构,在元素的插入,删除,搜索操作上具有常数平均时间复杂度O(1);

hashtable名词

散列函数:负责将某一元素映射为索引。

碰撞(collision):不同的元素被映射到相同的位置。

解决碰撞的方法:线性试探法,二次试探法,开链等。

负载系数:元素个数除以表格大小。

主集团:平均插入成本的增长幅度,远高于负载系数的成长幅度。

次集团:hashtable若采用二次探测,则若两个元素经hash function计算出来的位置相同,则插入时所试探的位置也相同,造成某种浪费。

开链:在每个表格元素中位置一个list。

桶(bucket):hashtable表格内的每个元素。

hashtable组织方式

节点类

template <class _Val>
struct _Hashtable_node
{
_Hashtable_node* _M_next;
_Val _M_val;
};

hashtable的节点和list节点相类似。

一个Node的指针指向每个桶上列表的首个元素,而桶上的链表首地址存放在vector向量中。

typedef _Hashtable_node<_Val> _Node;
vector<_Node*,_Alloc> _M_buckets;

hashtable迭代器的自增操作符定义如下:

template <class _Val, class _Key, class _HF, class _ExK, class _EqK,
class _All>
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>&
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>::operator++()
{
const _Node* __old = _M_cur; //当前节点的副本
_M_cur = _M_cur->_M_next; //前进一个节点
if (!_M_cur) { //到达当前链表的末端,则转向下一个桶的链表开头
//获得节点副本所在的桶编号
size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val);
//当桶为空,且未到达桶向量的末尾,则继续下一个桶
while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size())
//当前节点指针指向下一个桶的节点链表开头
_M_cur = _M_ht->_M_buckets[__bucket];
}
return *this;
}

hashtable类定义有五个模板参数

template <class _Val, class _Key, class _HashFcn,
class _ExtractKey, class _EqualKey, class _Alloc>
class hashtable;

_Val 值类型

_Key 键类型

_HashFcn 哈希函数

_ExtractKey 提取键的方法

_EqualKey 判断键相等的方法

_Alloc 分配器类型

hashtable插入操作

insert_unique不允许值重复的插入:

 pair<iterator, bool> insert_unique(const value_type& __obj)
{
resize(_M_num_elements + 1);
return insert_unique_noresize(__obj);
}

该函数首先调用resize函数,传入当前元素加一的值,看是否需要进行扩容。处理完成后,再将值__obj插入到hashtable中。

resize函数的定义如下:

template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::resize(size_type __num_elements_hint)
{
const size_type __old_n = _M_buckets.size(); //桶数量的旧值
if (__num_elements_hint > __old_n) { //传入的新值大于旧值
//从素数表中查找最接近新值的桶数量__n
const size_type __n = _M_next_size(__num_elements_hint);
if (__n > __old_n) { //得到的桶数量大于旧值:需要扩容
//分配容量为__n(新桶数量)的桶向量
vector<_Node*, _All> __tmp(__n, (_Node*)(0),
_M_buckets.get_allocator());
__STL_TRY {
//对旧hashtable中的每个桶
for (size_type __bucket = 0; __bucket < __old_n; ++__bucket) {
_Node* __first = _M_buckets[__bucket]; //取得桶中链表首地址
while (__first) { //未到达旧桶中链表的末尾
//根据链表节点中存放的元素数值_M_val,以新的桶数量__n,
//计算其在新的hashtable中的桶编号(rehash)
size_type __new_bucket = _M_bkt_num(__first->_M_val, __n);
//更新当前桶中节点链表头的指向,使其指向链表中的下一个元素
_M_buckets[__bucket] = __first->_M_next; //以下两句是增长新桶中的链表,把新节点加进去。
//链表首地址的后继指向新hashtable中计算出来的新桶中的链表开头
__first->_M_next = __tmp[__new_bucket];
//新hashtable,新桶的链表开头指向__first
__tmp[__new_bucket] = __first; //更新__first至原有的__first->_M_next,即在原hashtable原桶中的链表中前进一个元素。
__first = _M_buckets[__bucket];
}
}
//当旧hashtable中的元素都重新hash到新桶向量后
_M_buckets.swap(__tmp); //将新桶向量与旧桶向量相互交换。
//旧桶数据存放在__tmp向量中,当离开此范围时,__tmp作为一个局部变量,其空间会被自动释放。
}
//发生异常后进行的回滚操作
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
//将新的桶向量中的每个桶
for (size_type __bucket = 0; __bucket < __tmp.size(); ++__bucket) {
//对桶中链表的每个节点
while (__tmp[__bucket]) {
_Node* __next = __tmp[__bucket]->_M_next;
_M_delete_node(__tmp[__bucket]); //删除当前节点
__tmp[__bucket] = __next; //前进一个节点
}
}
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
}
}
}

insert_unique_noresize的函数定义如下:

template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
pair<typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator, bool>
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::insert_unique_noresize(const value_type& __obj)
{
const size_type __n = _M_bkt_num(__obj); //将值映射到对应桶中,并获得桶编号
_Node* __first = _M_buckets[__n]; //桶中链表的开头 //桶中链表非空,则遍历链表
for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
//链表中存在与__obj值相同的节点
if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj)))
//返回相等位置处的迭代器,并附加插入失败标识。
return pair<iterator, bool>(iterator(__cur, this), false); //链表中不存在与待插入值相等的元素,
_Node* __tmp = _M_new_node(__obj); //创建新链表节点
__tmp->_M_next = __first; //将新节点的后继设为链表开头
_M_buckets[__n] = __tmp; //将桶的链表开头设为新插入的节点
++_M_num_elements; //增加hashtable元素数量
//返回插入位置的迭代器,并附加插入成功标识。
return pair<iterator, bool>(iterator(__tmp, this), true);
}

结合insert_unique函数中调用的两个函数接口的分析,可知:

  • 在进行insert_unique函数执行时,首先尝试对桶向量进行扩容,然后再向hashtable中插入元素。
  • hashtable的扩容操作类似于vector的自增长过程,都经过1.申请更大空间;2.将原容器中的数据转移到新的容器中;3.清理原容器的空间;三部曲。
  • 与vector容器的自增长所不同的是,在数据转移的过程中,hashtable需要根据新的桶数量,对数据进行重新映射。

insert_equal允许重复的插入:

  iterator insert_equal(const value_type& __obj)
{
resize(_M_num_elements + 1);
return insert_equal_noresize(__obj);
}

与insert_unique相比较,函数在扩容后调用允许重复值的非扩充插入函数insert_equal_noresize:

template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::insert_equal_noresize(const value_type& __obj)
{
const size_type __n = _M_bkt_num(__obj); //将值映射到对应桶中,并获得桶编号
_Node* __first = _M_buckets[__n]; //桶中链表的开头 ///桶中链表非空,则遍历链表
for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
//链表中存在与__obj值相同的节点,则执行插入操作并返回
if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj))) {
_Node* __tmp = _M_new_node(__obj); //创建新链表节点
__tmp->_M_next = __cur->_M_next; //将插入节点的后继设为当前节点的后继
__cur->_M_next = __tmp; //当前节点的后继设为插入节点
++_M_num_elements; //更新元素个数
return iterator(__tmp, this); //完成插入操作,返回
} //链表中没有与插入元素值相同的节点
_Node* __tmp = _M_new_node(__obj); //创建新链表节点
__tmp->_M_next = __first; //将新节点的后继指向链表开头
_M_buckets[__n] = __tmp; //将桶的链表开头指向新插入的节点
++_M_num_elements; //更新元素个数
return iterator(__tmp, this); //完成插入操作,返回
}

insert_unique_noresize与insert_equal_noresize的比较:

  • insert_unique_noresize先在hashtable的某个桶中查找与插入值相同的节点,若找到,则直接返回,插入失败。否则,在桶的链表的头部插入新节点。
  • insert_equal_noresize先在hashtable的某个桶中查找与插入值相同的节点,若找到,则在其后插入新节点。否则,在桶的链表的头部插入新节点。

hashtable的删除和复制

hashtable删除

template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::clear()
{
//遍历桶向量
for (size_type __i = 0; __i < _M_buckets.size(); ++__i) {
_Node* __cur = _M_buckets[__i]; //当前桶
while (__cur != 0) { //遍历桶中链表
_Node* __next = __cur->_M_next; //保存后继节点地址
_M_delete_node(__cur); //析构指针所指向的对象,并释放节点
__cur = __next; //指针前进
}
_M_buckets[__i] = 0; //链表开头指向空
}
_M_num_elements = 0; //更新元素数量为0
}

hashtable复制

template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::_M_copy_from(const hashtable& __ht)
{
_M_buckets.clear(); //清空原桶向量
_M_buckets.reserve(__ht._M_buckets.size()); //为桶向量重新分配内存,是其能够容纳被复制对象
//初始化桶向量中的列表开头为空
_M_buckets.insert(_M_buckets.end(), __ht._M_buckets.size(), (_Node*) 0);
__STL_TRY {
//遍历桶
for (size_type __i = 0; __i < __ht._M_buckets.size(); ++__i) {
const _Node* __cur = __ht._M_buckets[__i]; //被复制散列(源散列)的某个桶中链表开头
if (__cur) { //链表非空
_Node* __copy = _M_new_node(__cur->_M_val); //创建链表开头节点副本
_M_buckets[__i] = __copy; //更新目标散列对应桶的链表开头 for (_Node* __next = __cur->_M_next; //对链表中剩下的元素,依次
__next;
__cur = __next, __next = __cur->_M_next) {
__copy->_M_next = _M_new_node(__next->_M_val); //创建节点副本
__copy = __copy->_M_next; //在列表中前进
}
}
}
_M_num_elements = __ht._M_num_elements; //更新元素数量
}
__STL_UNWIND(clear()); //复制发生异常时回滚操作
}

小结

  • hashtable不依赖元素的随机性,假设元素分布在一个相对固定的范围内,类似于一种字典结构。
  • hashtable可提供常数复杂度的插入,删除,搜索操作,但是需要创建哈希表,是以空间的代价换取时间的高效。
  • 树结构提供对数时间复杂度的元素搜索操作,依赖于元素的随机性。

STL关联容器值hashtable的更多相关文章

  1. STL关联容器

    这里简单学习一下STL关联容器,主要是map.multimap.set.multiset以及unordered_map.前四个底层实现都是利用红黑树实现的,查找算法时间复杂度为\(O(log(n))\ ...

  2. STL关联容器的基本操作

    关联容器 map,set map map是一种关联式容器包含 键/值 key/value 相当于python中的字典不允许有重复的keymap 无重复,有序 Map是STL的一个关联容器,它提供一对一 ...

  3. C++ STL map容器值为指针时怎么释放内存

    最近在使用STL中map时,遇到了一个问题,就是当map中值为指针对象时怎么释放内存? // 站点与TCP连接映射表 (key为ip_port_stationCode, value为 clientSo ...

  4. STL关联容器总结

    有序的都不带unordered,即如下: set multiset map multimap 其中带multi的表示关键字可以重复 无序的带unordered,如下: unordered_map un ...

  5. STL 笔记(二) 关联容器 map、set、multimap 和 multimap

    STL 关联容器简单介绍 关联容器即 key-value 键值对容器,依靠 key 来存储和读取元素. 在 STL 中,有四种关联容器,各自是: map 键值对 key-value 存储,key 不可 ...

  6. STL List容器

    转载http://www.cnblogs.com/fangyukuan/archive/2010/09/21/1832364.html 各个容器有很多的相似性.先学好一个,其它的就好办了.先从基础开始 ...

  7. STL——关联式容器

    一.关联式容器 标准的STL关联式容器分为set(集合)/map(映射表)两大类,以及这两大类的衍生体multiset(多键集合)和 multimap(多键映射表).这些容器的底层机制均以RB-tre ...

  8. STL之关联容器的映射底层

    STL的关联容器有set, map, multiset, multimap.用于实现它们的底层容器有划入标准的rb_tree和待增加标准的hashtable. 底层容器rb_tree为上层容器提供了一 ...

  9. 《STL源码剖析》——第五、六:关联容器与算法

    第五章.关联容器  5.0.关联容器 标准的STL关联式容器分为set(集合)和map(映射表)两大类,以及这两大类的衍生体multiset(多键集合)和multimap(多键映射表).这些容器的底层 ...

随机推荐

  1. 使用node来搭建简单的后台业务

    现在作为一个前端开发人员,越来越多的技术需要学习,近几天学习了下node.js,在很多前端以及后端应用了该技术,现在记录下自己摸索的一些简单的知识记录下来. 我的博客都是直接分享应用方法,没有说明一些 ...

  2. [USACO12DEC]第一!First!(字典树,拓扑排序)

    [USACO12DEC]第一!First! 题目描述 Bessie has been playing with strings again. She found that by changing th ...

  3. 面向对象原生js轮播图

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. jquery 父,子,兄弟节点获取

    jquery 父,子,兄弟节点获取 jQuery.parent(expr)           //找父元素 jQuery.parents(expr)          //找到所有祖先元素,不限于父 ...

  5. CSS3文本阴影、边框阴影

    CSS3添加阴影 一.使用text-shadow属性为文本添加阴影 二.使用box-shadow属性为边框添加阴影 一.为文本添加阴影 text-shadow     使用text-shadow,可以 ...

  6. AC自动机及其模板

    模板 #include<queue> #include<stdio.h> #include<string.h> using namespace std; ; ; ; ...

  7. opencv_python_基本图像处理

    https://www.e-learn.cn/content/python/2694135 https://blog.csdn.net/Eastmount/article/details/817488 ...

  8. (转载)《利用Python进行数据分析·第2版》电子书

    https://www.jianshu.com/p/04d180d90a3f https://www.jianshu.com/p/04d180d90a3f https://www.jianshu.co ...

  9. 大牛整理最全Python零基础入门学习资料

    大牛整理最全Python零基础入门学习资料 发布时间:『 2017-11-12 11:56 』     帖子类别:『人工智能』  阅读次数:3504 (本文『大牛整理最全Python零基础入门学习资料 ...

  10. 移动端续讲及zepto移动端插件外加touch插件介绍

    媒体查询:针对不同设备,显示不同的样式. 设备像素比:dpr  device-piexl-ratio 在he开发中,要一个3陪高清图片: 1080>=320*3 (主要是为了解决图片的失真问题) ...