有关于STL中的哈希表一些基础知识
- 为什么要设计哈希表——查找元素效率高 O(1),和关联式容器相比,无序容器擅长通过指定键查找对应的值;但对于使用迭代器遍历容器中存储的元素,无序容器的执行效率则不如关联式容器。
- 通过什么机制实现的——关键字通过哈希函数O(1)可以直接确定哈希地址,常见的有直接定址法、除留余数法
- 会产生什么问题——不同的关键字可能会得到相同的哈希地址,称为哈希碰撞
- 如何解决哈希碰撞——闭散列找空位置(每次向后找1或i^2),或者开散列在同一个哈希地址的键值对存在同一个哈希桶中,哈希桶可以是单链表或红黑树,哈希地址中存放单链表头结点或红黑树根节点存放在
- 闭散列和开散列比较——开散列更加实用,一是开散列的负载因子更大(闭散列负载因子不能超过 1,一般建议控制在 0.0 ~ 0.7 之间;开散列负载因子可以超过 1,一般建议控制在 0.0 ~ 1.0 之间),二是开散列在极端情况(所有关键字都得到同一个哈希地址,哈希表退化为单链表O(n))还可以采取红黑树O(log n) 作为哈希桶
四种数据存储结构:
- 顺序存储:逻辑上相邻的数据元素其对应的物理存储位置也是相邻的)
- 链式存储:每个元素一部分存储元素值本身,另一部分用于存放指向下一个元素的指针
- 索引存储:索引表中的每一项包括关键字和地址,关键字是能够唯一标示一个数据元素的数据项,地址是指示数据元素的存储地址或者存储区域的首地址的
- 散列存储:将数据元素存储在一个连续的区域,每一个数据元素的具体存储位置是根据该数据的关键字值,通过散列(哈希)函数直接计算出来的
四种无序容器 | 存储元素 | key能否重复 | key能否修改 |
---|---|---|---|
unordered_map | <key,value> | NO | NO |
unordered_multimap | <key,value> | YES | NO |
unordered_set | <value,value> | NO | NO |
unordered_multise | <value,value> | YES | NO |
哈希表是牺牲空间换取时间,通过使用额外的数组、set或是map来存放数据,才能实现快速的查找。
哈希函数
要寻找哈希表中的元素,只需要通过哈希函数对该元素关键字进行计算,就可以得出存储地址,也就是说元素的存储位置与它的关键码之间能够建立一种映射的关系
直接定址法
关键字的某个线性函数作为哈希地址
H a s h ( k e y ) = a × k e y + b Hash(key)=a×key+b Hash(key)=a×key+b
除留余数法
散列表的长度是 m , p m,p m,p是0~m中最大的质数
H a s h ( k e y ) = k e y ( m o d ) p Hash(key)=key (mod) p Hash(key)=key(mod)p
哈希碰撞
不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为 哈希冲突 或 哈希碰撞,把具有不同关键码而具有相同哈希地址的数据元素称为 同义词。
线性探测
从发生冲突的位置依次向后查找,直到找到一个空位置
二次探测
从发生冲突的位置向后查找,每次往后移动 i^2 个位置,直到找到空位置,也就是每次往后移动的距离都会增大,不容易导致数据堆积
链地址法
首先对关键字集合通过哈希函数计算出哈希地址,将有相同哈希地址的关键字放在同一个子集合,也就是一个哈希桶,在哈希桶内部关键字通过单链表连接,头结点放在哈希表中
红黑树作为哈希桶(地址全都相同的情况)
所有元素全部产生冲突,最终都放到了同一个单链表中,此时该哈希表增删查改的效率就退化成 O ( N ) ,我们就可以用红黑树来作为哈希桶的数据结构。红黑树搜索时间复杂度是 O(logN)
但有些地方也会选择不把桶中的单链表结构换成红黑树结构,因为随着哈希表中数据的增多,该哈希表的负载因子也会逐渐增大,最终会触发哈希表的增容条件,此时该哈希表当中的数据会全部重新插入到另一个空间更大的哈希表,此时同一个桶当中冲突的数据个数也会减少,因此不做处理问题也不大。
具体函数操作
参考:C++ STL unordered_map容器用法详解
容器模板
template < class Key, //键值对中键的类型
class T, //键值对中值的类型
class Hash = hash<Key>, //容器内部存储键值对所用的哈希函数
class Pred = equal_to<Key>, //判断各个键值对键相同的规则
class Alloc = allocator< pair<const Key,T> > // 指定分配器对象的类型
template < class Key, //键(key)的类型
class T, //值(value)的类型
class Hash = hash<Key>, //底层存储键值对时采用的哈希函数
class Pred = equal_to<Key>, //判断各个键值对的键相等的规则
class Alloc = allocator< pair<const Key,T> > // 指定分配器对象的类型
> class unordered_multimap;
template < class Key, //容器中存储元素的类型
class Hash = hash<Key>, //确定元素存储位置所用的哈希函数
class Pred = equal_to<Key>, //判断各个元素是否相等所用的函数
class Alloc = allocator<Key> //指定分配器对象的类型
> class unordered_set;
template < class Key, //容器中存储元素的类型
class Hash = hash<Key>, //确定元素存储位置所用的哈希函数
class Pred = equal_to<Key>, //判断各个元素是否相等所用的函数
class Alloc = allocator<Key> //指定分配器对象的类型
> class unordered_multiset;
创建容器
- 调用模板类的默认构造函数,创建空的容器
std::unordered_map<std::string, std::string> umap;
- 创建的同时初始化
std::unordered_map<std::string, std::string> umap{
{
"Python教程","http://c.biancheng.net/python/"},
{
"Java教程","http://c.biancheng.net/java/"},
{
"Linux教程","http://c.biancheng.net/linux/"} };
- 调用模板中的拷贝构造函数,将现有容器中存储的键值对,复制给新建的容器
std::unordered_map<std::string, std::string> umap2(umap);
- 调用移动构造函数,即以右