文章目录
前言:
在学习本节课之前,请大家思考这样一个问题:如果我们要在一个长度为 的随机整数序列 中统计每 个数出现的次数,可以用什么方法?
不难想到,我们可以建立一个数组 ,然后将整数序列A中的元素映射到数组的下标,最后遍历整个数组 即可统计出随机整数序列 中每个数出现的次数。但是如果数列 中数比较大呢?我们就可以借助Hash 表来解决这个问题。
一 Hash表
散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存储存位置的数据结构。也就是说,它 通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度 当我们要对若干复杂信息进行统计时,可以用Hash函数把这些复杂信息映射到一个容易维护的值域内。
比如刚才那个问题,当N过于大时,我们可以设计Hash函数为 ,其中P是 一个较大的质数,但不超过N,这样这个Hash函数就把数列 中的值映射到了不同的区域进行储存,然 后继续进行后面的操作。
总的来说, 其基本原理是:使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函 数,也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标,hash值)相对应,于是 用这个数组单元来存储这个元素。
但是,由于值域变简单、范围变小,有可能造成两个不同的原始信息被Hash函数映射为相同的值,这种 情况叫做Hash冲突,我们下面会讲解如何处理这种冲突。
总结一下,Hash表主要包括两个基本操作:
1.计算Hash函数的值
2.定位到对应链表中依次遍历、比较。
1 Hash函数的构造
hash函数构造原则
1.简答易算
2.能够使地址均匀分布
直接定址法 H(key)=a*key+b
除留余数法 H(key)=key MOD p,p<=m (最简单,最常用)p的选取尽量是一个质数,并且离2的 整次幂尽可能的远
2 拉链法处理hash冲突模板
拉链法是借助邻接表实现的,当目标元素经过Hash函数计算后所得到的值与之前的元素计算得出的值相 同,就将其插入以这个Hash值开头的链表中去。
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 3;
// 取大于1e5的第一个质数,取质数冲突的概率最小 可以百度
//* 开一个槽 h
int h[N], e[N], ne[N], idx; //邻接表
//分别存储链表头,数值,指向的下一元素编号
void insert(int x) {//插入
// c++中如果是负数 那他取模也是负的 所以 加N 再 %N 就一定是一个正数
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx++;
}
bool find(int x) {//查找
//用上面同样的 Hash函数 讲x映射到 从 0-1e5 之间的数
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i]) {
if (e[i] == x) {
return true;
}