哈希
一、概念
1. Hash函数
根据关键字直接计算出元素所在位置的函数为哈希函数;
2. 哈希表
根据设定的哈希函数和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象”作为记录在表中的存储位置,所得的表便称为哈希表,这一映象过程称为哈希造表或散列,所得存储位置称为哈希地址或散列地址;
二、构造哈希函数
1. 自然溢出
利用 unsigned long long
溢出后会自动模
2
63
−
1
2^{63} - 1
263−1 ,所以,可利用此性质计算哈希值;
2. 直接定址法
取关键字或关键字的某个线性函数值为其散列地址,即 H ( K ) = K H(K) = K H(K)=K 或 H ( K ) = a ∗ K + b H(K) = a * K + b H(K)=a∗K+b (其中 a , b a, b a,b 为常数) ;
3. 除后余数法
取关键字被不大于散列表表长 m m m 的数 p p p 除后所得的余数为哈希函数,即 H ( K ) = K m o d p ( p ≤ m ) H(K) = K \; mod \; p(p \leq m) H(K)=Kmodp(p≤m) ;
一般可选 p p p 为质数, p p p 的大小根据 m m m 与 K K K 决定;
4. 平方取中法
由于两数乘积的中间几位与两数的每一位都相关,所以可取关键字平方后的中间几位为哈希函数;
5. 数字分析法
各种符号在某几位上出现的频率大致相同时,可选用关键字的某几位组合成哈希地址;
6. 折叠法
折叠法是在每一位上各符号出现概率大致相同的情况下,将关键字按要求的长度分成位数相似的几段,然后把各段重叠在一起相加并去掉进位,以所得的和作为地址的方法;
7. 随机数法
随机数法即当关键字长度不等时选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H ( K ) = r a n d ( K ) H(K) = rand(K) H(K)=rand(K) ;
8. 设计哈希函数因素
- 计算哈希函数所需时间;
- 关键字的长度;
- 哈希表的大小;
- 关键字的分布情况;
- 记录的查找频率;
三、冲突
1. 定义
两个不同的关键字具有相同的存储位置,即多个关键字通过hash函数得到的地址是同一个地址的情况;
2. 冲突因素
-
装填因子 α \alpha α
装填因子 α \alpha α 是指哈希表中己存入的元素个数 n n n 与哈希表的大小 m m m 的比值,即 α = n / m \alpha = n / m α=n/m ;
α \alpha α 越小,发生冲突的可能性越小,反之,发生冲突的可能性就越大;
但是, α \alpha α 太小又会造成大量存贮空间的浪费,因此应兼顾存储空间和冲突两个方面;
-
所构造的哈希函数;
-
解决冲突的方法;
3. 解决冲突的方法
开放地址法
开放地址就是表中尚未被占用的地址,当新插入的记录所选地址已被占用时,即转而寻找其它尚开放的地址;
链地址法
将具有相同哈希地址的记录链成一个单链表,则 m m m 个哈希地址就设 m m m 个单链表,然后用一个数组将 m m m 个单链表的表头指针存储起来,形成一个动态的结构;
再哈希法
即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生;
建立公共溢出区
假设哈希函数的值域为 [ 1 , m ] [1, m] [1,m] ,则设哈希表 [ 1 , m ] [1, m] [1,m] 为基本表;
在此基础上,再建立一个溢出表,在之后的哈希操作中,无论关键字的同义词生成怎样的哈希地址,一旦发生冲突,就将其放入溢出表中;
四、例题
题目
题意为有一个字符串,进行 m m m 次询问,每次询问两个区间,判断两区间是否相等;
分析
由于需要判断字符串区间相等,想到哈希;
由于若对于每一次询问都计算一次哈希,则会超时,则可使用区间哈希;
对于已知长度为
l
e
n
len
len 的序列哈希数组为
h
a
s
h
1
hash1
hash1 ,则对于其区间
[
l
,
r
]
[l, r]
[l,r] ,有
h
a
s
h
(
l
,
r
)
=
h
a
s
h
1
[
r
]
−
h
a
s
h
1
[
l
−
1
]
∗
p
r
−
l
+
1
hash(l, r) = hash1[r] - hash1[l - 1] * p^{r - l + 1}
hash(l,r)=hash1[r]−hash1[l−1]∗pr−l+1
推导如下,
区间 [ 1 , r ] [1, r] [1,r] 的哈希值则为 h a s h 1 [ r ] hash1[r] hash1[r] ,区间 [ 1 , l − 1 ] [1, l - 1] [1,l−1] 的哈希值则为 h a s h 1 [ l − 1 ] hash1[l - 1] hash1[l−1] ,但由于所求区间为单独的区间,所以权值 P P P 也应减去,所以对于区间 [ 1 , l − 1 ] [1, l - 1] [1,l−1] 的哈希值应乘上其空余的权值即 p r − l + 1 p^{r - l + 1} pr−l+1 ,再使用类似前缀和的方法得到上述式子;
则此题使用此方法即可;
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#define P 19
#define MAXN 1000005
using namespace std;
int n, len;
char s[MAXN];
unsigned long long hash1[MAXN], p[MAXN];
unsigned long long Hash (int l, int r) {
return (hash1[r] - hash1[l - 1] * p[r - l + 1]);
}
int main() {
scanf("%s", s + 1);
scanf("%d", &n);
len = strlen(s + 1), p[0] = 1, hash1[0] = 0;
for (int i = 1; i <= len; i++) {
hash1[i] = hash1[i - 1] * P + s[i] - 'A';
p[i] = p[i - 1] * P;
}
for (int num = 1; num <= n; num++) {
int l1, l2, r1, r2;
scanf("%d %d %d %d", &l1, &r1, &l2, &r2);
if (Hash(l1, r1) == Hash(l2, r2)) printf("Yes\n");
else printf("No\n");
}
return 0;
}