哈希表

First Post:
Last Update:

本章无需前置知识

哈希表

哈希表之前有讲过,就不再举例子了,课上将直接讲解

俩种解决哈希冲突的方法

  • 拉链法
  • 开放寻址法

例题:

维护一个集合,支持如下几种操作:

  1. I x,插入一个数 x;
  2. Q x,询问数 x 是否在集合中出现过;

现在要进行 N 次操作,对于每个询问操作输出对应的结果。

输入格式

第一行包含整数 N,表示操作数量。

接下来 N 行,每行包含一个操作指令,操作指令为 I xQ x 中的一种。

输出格式

对于每个询问指令 Q x,输出一个询问结果,如果 x 在集合中出现过,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤N≤10^5
−10^9≤x≤10^9

输入样例:

5
I 1
I 2
I 3
Q 2
Q 5

输出样例:

Yes
No

拉链法

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int h[N], e[N], ne[N], idx;

int ha(int x)
{
return (x % N + N) % N;
}

void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void insert(int x)
{
int t = ha(x);

add(t, x);
}

bool query(int x)
{
int t = ha(x);

for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (j == x) return true;
}

return false;
}

int main()
{
int T;
cin >> T;

memset(h, -1, sizeof h);

while (T -- )
{
char op; int x;
cin >> op >> x;

if (op == 'I') insert(x);
else
{
if (query(x)) puts("Yes");
else puts("No");
}
}

return 0;
}

开放寻址法

#include <iostream>
#include <cstring>

using namespace std;

const int N = 2e5 + 10;

int h[N], null = 0x3f3f3f3f;

int ha(int x)
{
return (x % N + N) % N;
}

void insert(int x)
{
int t = ha(x);

while (h[t] != null && h[t] != x)
{
t = (t + 1) % N;
}

h[t] = x;
}

bool query(int x)
{
int t = ha(x);

while (h[t] != x && h[t] != null)
{
t = (t + 1) % N;
}

return h[t] == x;
}

int main()
{
int T;
cin >> T;

memset(h, 0x3f, sizeof h);

while (T -- )
{
char op;
int x;
cin >> op >> x;

if (op == 'I') insert(x);
else
{
if (query(x)) puts("Yes");
else puts("No");
}
}
return 0;
}

字符串哈希

全称字符串前缀哈希法,把字符串变成一个p进制数字(哈希值),实现不同的字符串映射到不同的数字。
对形如 X1X2X3⋯Xn−1Xn 的字符串,采用字符的ascii 码乘上 P 的次方来计算哈希值。

映射公式 (X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0)modQ
注意点:

  • 任意字符不可以映射成0,否则会出现不同的字符串都映射成0的情况,比如A,AA,AAA皆为0冲突问题:
  • 通过巧妙设置P (131 或 13331) , Q (2^64)的值,一般可以理解为不产生冲突。

例题:

给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2,请你判断 [l1,r1]和 [l2,r2] 这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式

第一行包含整数 n 和 m,表示字符串长度和询问次数。

第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。

接下来 m 行,每行包含四个整数 l1,r1,l2,r2,表示一次询问所涉及的两个区间。

注意,字符串的位置从 1 开始编号。

输出格式

对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤10^5

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes
#include <iostream>
#include <algorithm>

using namespace std;

typedef unsigned long long ULL;

const int N = 100010, P = 131;

int n, m;
char str[N];
ULL h[N], p[N];

ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
scanf("%d%d", &n, &m);
scanf("%s", 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 -- )
{
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);

if (get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}

return 0;
}

我们数据结构学习到这里就告一段落了