哈希表
哈希表
哈希表的作用
哈希表是在键和值之间通过散列函数建立对应关系,将一个庞大的值域映射到一个比较小的空间,并使得元素的查找可以以\(O(1)\)的效率进行
例将\(0\sim 10^9 \to 0\sim 10^5\)
常见的散列函数
线性定址法:直接取关键字的某个线性函数作为存储地址
除留余数法:将关键字对某一小于散列表长度的数p取余的结果作为存储地址
平方取中法:对关键字取平方,然后将得到结果的中间几位作为存储地址
折叠法:将关键字分割为几部分,然后将这几部分的叠加和作为存储地址
……
存储结构
地址冲突解决方法:
开放地址法:
-
线性探测法:当发生冲突时,就顺序查看下一个存储位置
-
平方探测法:以$12,-12,22,-22,\dots $测新的存储位置能否存储元素
-
再散列法:利用两个散列函数,当通过第一个散列函数发生冲突时,再利用第二个散列函数计算出地址增量
-
伪随机数法: 当发生地址冲突时,加入一个随机数作为地址增量寻找新的存储地址
拉链法:
将具有相同存储地址的关键字链成一单链表
实现
拉链法,散列函数使用除留余数法,模p取不大于表长且最接近表长的素数时效果最好,所以N一般要先测定
int h[N], e[N], ne[N], idx;
// h数组是一排头节点,以元素散列后的值为下标
// e数组模拟单链表,存储原始值,插在h数组的头节点后
void insert(int x) {
int k = (x % N + N) % N; // 保证k为正数
e[idx] = x;
ne[idx] = h[k]; // 头插法,h[x]作为链表头
h[k] = idx++;
}
bool find(int x) {
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i]) {
if (e[i] == x) return true;
}
return false;
}
开放寻址法,数组一般开到需要的2~3倍
const int N = 200003, null = 0x3f3f3f3f; // 0x3f详见Temp.md-无穷大
int h[N];
memset(h, 0x3f, sizeof(h));
// 如果x在哈希表中存在则返回其位置,如果不存在则返回应该存放的位置
int find(int x) {
int k = (x % N + x) % N;
while (h[k] != null && h[k] != x) { // null代表为空
k++;
if (k == N) k = 0;
}
return k;
}
// 因为find()返回的是存在的位置或应该存储的位置,所以插入直接修改进行
int k = find(x);
h[k] = x;
STL库中的哈希表
头文件#include <unordered_map>
声明
unordered_map<elemType_1, elemType_2> var_name; //声明一个没有任何元素的哈希表,
//其中elemType_1和elemType_2是模板允许定义的类型,如要定义一个键值对都为int的哈希表:
unordered_map<int, int> map;
初始化
unordered_map<int, int> hmap{ {1,10},{2,12},{3,13} };
//如果知道要创建的哈希表的元素个数时,也可以在初始化列表中指定元素个数
unordered_map<int, int> hmap{ {{1,10},{2,12},{3,13}},3 };
添加元素
通过下标运算添加元素:
mapName[key] = value;
//在相同key中新的值会覆盖旧的值
通过insert()
函数添加元素:
napName.insert({key,value});
//在相同key中第二次插入会失败
//试过了,不能一次添加多个键值对...
复制构造,通过其他已初始化的哈希表来初始新的表:
unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
unordered_map<int, int> mapName1(mapName);
STL中哈希表的常用函数
begin( )
函数:该函数返回一个指向哈希表开始位置的迭代器
unordered_map<int,int>::iterator iterName = mapName.begin();
//申请迭代器,并初始化为哈希表的起始位置
end( )
函数:作用于begin函数相同,返回一个指向哈希表结尾位置的下一个元素的迭代器
unordered_map<int, int>::iterator iterName = mapName.end();
cbegin()
和cend()
:这两个函数的功能和begin()与end()的功能相同,唯一的区别是cbegin()
和cend()
是面向不可变的哈希表
const unordered_map<int, int> hmap{ {1,10},{2,12},{3,13} };
unordered_map<int, int>::const_iterator iterName_a = mapName.cbegin();
//注意这里的迭代器也要是不可变的const_iterator迭代器
unordered_map<int, int>::const_iterator iterName_b = mapName.cend();
empty()
函数:判断哈希表是否为空,空则返回true,非空返回false
bool isEmpty = mapName.empty();
size()
函数:返回哈希表的大小
int size = mapName.size();
erase()
函数: 删除某个位置的元素,或者删除某个位置开始到某个位置结束这一范围内的元素, 或者传入key值删除键值对
unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
unordered_map<int, int>::iterator iter_begin = mapName.begin();
unordered_map<int, int>::iterator iter_end = mapName.end();
hmap.erase(iter_begin); //删除开始位置的元素
hmap.erase(iter_begin, iter_end); //删除开始位置和结束位置之间的元素
hmap.erase(3); //删除key==3的键值对
at()
函数:根据key查找哈希表中的元素
unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
int elem = hmap.at(3);
clear()
函数:清空哈希表中的元素
hmap.clear()
find()
函数:以key作为参数寻找哈希表中的元素,如果哈希表中存在该key值则返回该位置上的迭代器,否则返回哈希表最后一个元素下一位置上的迭代器
unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
unordered_map<int, int>::iterator iter;
iter = mapName.find(2); //返回key==2的迭代器,可以通过iter->second访问该key对应的元素
//if(iter != mapName.end()) cout << iter->second;
bucket()
函数:以key寻找哈希表中该元素的储存的bucket编号(unordered_map的源码是基于拉链式的哈希表,所以是通过一个个bucket存储元素)
int pos = mapName.bucket(key);
如果 HashMap 的每个 bucket 里只有一个 Entry 时,HashMap 可以根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的情况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止
归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[]
数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 Hash 算法来决定其存储位置;当需要取出一个 Entry 时,也会根据 Hash 算法找到其存储位置,直接取出该 Entry。由此可见:HashMap 之所以能快速存、取它所包含的 Entry,完全类似于现实生活中母亲从小教我们的:不同的东西要放在不同的位置,需要时才能快速找到它。
当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 0.75
,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap 的 get() 与 put() 方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。
bucket_count()
函数:该函数返回哈希表中存在的存储桶总数(一个存储桶可以用来存放多个元素,也可以不存放元素,并且bucket的个数大于等于元素个数)
int count = mapName.bucket_count();
count()
函数: 统计某个key值对应的元素个数, 因为unordered_map不允许重复元素,所以返回值为0或1
int count = mapName.count(key);
通过迭代器遍历哈希表:
unordered_map<int, int> mapName{ {1,10},{2,20},{3,30} };
unordered_map<int,int>::iterator iter = mapName.begin();
for(;iter != mapName.end();iter++) {
cout << "key: " << iter->first << " value: " << iter->second << endl;
}