一、哈希表的存储结构
哈希表的作用:将一堆复杂庞大的数据映射到较小的内存空间中(一般是将1e9的数据映射到N = 1e5或1e6的数组里),手写哈希表一般支持插入、查找。如果要用哈希表实现删除操作,一般会开一个bool标记,对要删除的点要进行特别标记。哈希表的时间复杂度可以认为是O(1)。
哈希表的创建主要考虑两个问题:1、哈希函数。2、处理映射冲突(拉链法、开放寻址法)
(1)拉链法
开一个一维数组h(N)来存储所有的哈希值,在h(N)数组的每一个槽上拉以一个链表存储该位置冲突的值
处理冲突的方式:当h(11) = 3,h(21)= 3,即11与21都映射到3上时,就产生了冲突,此时可以在3上拉一条链(即单链表,h(3)为该链表的头节点head)同一条链上元素的插入方式与单链表的头插相同,把head改成h(k)即可。
哈希表中映射后的数据范围N的选择一般遵循两个原则:1、为质数。2、离2的整次幂尽可能远
以下代码可以用来确定数组大小N
int flag;
for(int i = 100000;;i ++ ){
for(int j = 2;j * j <= i;j ++ ){
if(i % j == 0){
flag = false;
break;
}
}
if(flag){
cout << i;
break;
}
flag = true;
}
拉链法代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100003;
int h[N],e[N],ne[N],idx;
void insert(int x){
int k = (x % N + N) % N; //k为哈希值,+N再%N的操作确保k为正数
e[idx] = x;
ne[idx] = h[k];
h[k] = idx;
idx ++ ;
//h[k]相当于单链表里的head
}
bool query(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;
}
int main(){
int n,x;
cin >> n;
char op[2];
memset(h,-1,sizeof h);
while(n -- ){
cin >> op;
if(*op == 'I'){
cin >> x;
insert(x);
}
else{
cin >> x;
if(query(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
(2)开放寻址法
只开一个一维数组,不开链表
一维数组的长度一般要开到题目数据的2~3倍
处理冲突的方法:h(k)= x时,看看k处有没有元素,如果没有就放到这里,如果有就看k+1的位置,以此类推往后遍历。
null为约定的标志,h数组初始化为null,null在题目数据范围-1e9~1e9之外,当该位为null时表明次位为空,memset h数据时第二个参数传入0x3f因为memset修改的是一个字节,而int为四字节,所以这样就可以把h数组的每一位都初始化为0x3f3f3f3f了
#include<iostream>
#include<cstring>
using namespace std;
const int N = 200003,null = 0x3f3f3f3f;
int h[N];
int query(int x){
int k = (x % N + N) % N;
while(h[k] != null && h[k] != x){
k ++;
if(k == N) k = 0;
}
return k;
}
int main(){
int n;
char op[2];
cin >> n;
memset(h,0x3f,sizeof h);
while(n -- ){
int x;
cin >> op;
if(*op == 'I'){
cin >> x;
h[query(x)] = x;
}
else{
cin >> x;
if(h[query(x)] == null) puts("No");
else puts("Yes");
}
}
return 0;
}
(3)STL unordered_map
#include<iostream>
#include<unordered_map>
using namespace std;
const int N = 100003;
unordered_map<int,int> h;
int main(){
int n;
char op[2];
cin >> n;
while(n -- ){
int x;
int y;
cin >> op;
if(*op == 'I'){
cin >> x;
y = (x % N + N) % N;
h.insert({x,y});
}
else{
cin >> x;
if(h.find(x) == h.end()) puts("No");
else puts("Yes");
}
}
return 0;
}
二、字符串哈希方式——字符串前缀哈希法
字符串哈希就是把不同的字符串映射成不同的整数
关键是把字符串映射成一个p进制数字。对于一个长度为n的字符串s,这样定义哈希函数:。例如字符串abc,其哈希函数值为,即97 x 131^2 + 98 x 131^1 +99
处理哈希冲突的方式:P取131或13331、M取(把哈希函数值h定义为unsign long long,超过自动溢出,等价于取模),保证P与M互质,就可以认定在99.99%情况下不会产生哈希冲突,
求一个字符串的哈希值相当于求前缀和,求一个字符串字串的哈希值相当于求区间和
#include<iostream>
using namespace std;
const int N = 100010,P = 131;
typedef unsigned long long ULL;
int n,m;
char str[N];
//p[i]=P^i,h[1] = s[i - 1]的hash值
ULL h[N],p[N];
//预处理hash函数的前缀和
void init(){
p[0] = 1,h[0] = 0;
for(int i = 1;i <= n;i ++ ){
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i];
}
}
//计算s[l~r]的hash值
ULL get(int l1,int r1){
return h[r1] - h[l1 - 1] * p[r1 - l1 + 1];
}
int main(){
int l1,r1,l2,r2;
cin >> n >> m >> str + 1;
init();
while(m -- ){
cin >> l1 >> r1 >> l2 >> r2;
if(get(l1,r1) == get(l2,r2)) puts("Yes");
else puts("No");
}
return 0;
}