哈希表的简介与应用
将-109 ~ 109范围内的数映射到0 ~ 105中,是这题的重点。如何让一个较大的数变成一个较小的数呢?一般我们想将数的范围缩小是将这个数取模,想要将数据控制到什么范围内,就对这个数取模。比如这题,我们将输入的x mod 1e5之后就得到它的存储下标。什么意思呢?例如需要插入的数为1e8+648,那么将他储存在数组中下标为1e3+648的位置中。
不过还有一个问题,那就是取模后的重复问题,不同的数在取模时候可能是相同的,那么这时候如何处理呢?目前而言有两种方法,拉链法和开放寻址法。还有一点,当模的数为质数时,重复的概率较小,这里我们将范围取到1e5+3。
拉链法
所谓拉链法就是结合数组和单链表,以数组为起始点将取模后相同的以单链表的形式都储存在一个下标之下。
不会模拟单链表的可以看看我的这篇博客:用数组模拟链表_Kicamon的博客-CSDN博客,这里就不细讲单链表的构造了。
插入函数:
void insert(int x)
{
int t = (x % N + N) % N;//N是范围,即1e5+3。当x为负数时,取模后加上N在取模可以将其转变为正数。
e[idx] = x;//e为数据域,ne为指针域
ne[idx] = q[t];
q[t] = idx++;
}
查询函数:
bool find(int x)
{
int t = (x % N + N) % N;
for(int i = q[t];i != -1;i = ne[i])
if(e[i] == x) return 1;
return 0;
}
完整代码如下:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 3;
int q[N];
int e[N], ne[N], idx;
void insert(int x)
{
int t = (x % N + N) % N;
e[idx] = x;
ne[idx] = q[t];
q[t] = idx++;
}
bool find(int x)
{
int t = (x % N + N) % N;
for (int i = q[t]; i != -1; i = ne[i])
if (e[i] == x)return 1;
return 0;
}
int main()
{
memset(q, -1, sizeof q);
int n;
char op[2];
int x;
cin >> n;
while (n--)
{
cin >> op >> x;
if (*op == 'I')
insert(x);
else
{
if (find(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
开放寻址法
开放寻址法相较拉链法而言较为简单,数重复之后就将它后移一位,一直后移到空位上。读取的时候也是一样,往后查询。所以在插入的时候,就用find函数找到插入地址即可,不用在另写一个函数用于插入。
find函数:
const int N = 2e5+3,null = 0x3f3f3f3f;
int find(int x)
{
int t = (x % N + N) % N;
while(q[t] != x && q[t] != null)
{
t++;
if(t == N)t = 0;
}
return t;
}
完整代码如下:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 2e5 + 3,null = 0x3f3f3f3f;
int q[N];
int find(int x)
{
int t = (x % N + N) % N;
while (q[t] != x && q[t] != null)
{
t++;
if(t == N) t = 0;
}
return t;
}
int main()
{
memset(q, 0x3f, sizeof q);
int n;
cin >> n;
char op[2];
int x;
while (n--)
{
cin >> op >> x;
if (*op == 'I')q[find(x)] = x;
else
{
if (q[find(x)] != null)puts("Yes");
else puts("No");
}
}
return 0;
}
案例:字符串哈希
对于字符串的哈希是利用前缀哈希法,先预处理所有前缀的哈希,将其哈希值存储在数组中。例如abccbddf,预处理之后就是:
h[0] = 0;h[1] = “a”;h[2] = “ab”……
首先将字符视为P进制的数,并将其转化为十进制映射到h数组中。
例如abcd,映射为1* P3+2 * P2 +3 * P + 4 * P0,然后取模后就是映射的下标。
经验而言,当P为131或13331、取模数264时,取模后重复的概率极低,一般可以忽略不计。在声明h数组时,用unsigned long long就可以避免取模步骤,溢出结果与取模相同。
对比的方法就是对比两端字符串的哈希值。想要得到 l~r 的哈希值,只需要将l-1左移len(这段字符的长度)位,再用r减去即可。
获取哈希值的代码如下:
unsigned long long get(int l,int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
h和p函数的预处理:
p[0] = 0;
for(int i = 1;i <= n;i++)
{
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i];//str从下标1开始读入
}
完整代码如下:
#include<iostream>
using namespace std;
#define ULL unsigned long long
const int N = 1e5 + 3, P = 131;
char str[N];
int l1, r1, l2, r2;
ULL h[N], p[N];
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
int n, m;
cin >> n >> m;
cin >> str + 1;
p[0] = 1;
for (int i = 1; i <= n; i++)
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
while (m--)
{
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2))puts("Yes");
else puts("No");
}
return 0;
}