论一个算法【哈希】
hash的概念
即通过一种计算方式,将每个值映射到一个唯一对应的键值。并以关键字在地址集中的“象”作为记录在表中的存储位置,这种表便称为哈希表,这一映象过程称为哈希造表或散列,所得存储位置称为哈希地址或散列地址
举个栗子:设哈希函数为:
H
(
K
)
=
K
/
3
+
1
H(K)=K/3+1
H(K)=K/3+1,则构造关键字序列为 1、2、5、9、11、13、16、21、27 的哈希表(散列表)为:
不难发现。许多不同值的 H a s h Hash Hash键值相同,这时就要引入一个新的概念
冲突
理解起来并不难,即两个不同的关键字具有相同的存储位置,问题就在于如何解决,既然冲突无法避免的话,那又如何降低冲突的概率。在解决之前,我们还需知道冲突的可能性与哪些因素有关
- 装填因子:装填因子是指哈希表中己存入的元素个数 n n n与哈希表的大小 m m m 的比值,即 α = n / m α=n/m α=n/m。 α α α越小,发生冲突的可能性越小,反之,发生冲突的可能性就越大(很好理解吧)
- 所构造的哈希函数:构造的哈希函数不同,冲突的可能性也会不同,由此可见,一个好的 H a s h Hash Hash函数有多么重要
处理冲突的方法
1.开放地址法
开放地址就是,当新插入的记录所选地址已被占用时,便找另一个空的地址让它存储
(1)线性探测法
设散列函数
H
(
K
)
=
H(K)=
H(K)=
K
K
K
m
o
d
mod
mod
m
m
m,若发生冲突,则沿着一个探查序列逐个探查(也就是加上一个增量)
优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素
缺点:可能使第 i i i个哈希地址的同义词存入第 i + 1 i+1 i+1个哈希地址,这样本应存入第 i + 1 i+1 i+1个哈希地址的元素变成了第 i + 2 i+2 i+2个哈希地址的同义词,因此,可能出现很多元素在相邻的哈希,地址上“堆积”起来,大大降低了查找效率
(2)二次探测法
即 H i = ( H ( k ) + d i ) Hi=(H(k)+di) Hi=(H(k)+di) m o d mod mod m m m其中 d i = 1 2 , − 1 2 , 2 2 , − 2 2 , … , j 2 , − j 2 ( j ≤ m / 2 ) di =1^2,-1^2,2^2,-2^2,…,j^2,-j^2(j≤m/2) di=12,−12,22,−22,…,j2,−j2(j≤m/2)
2.链地址法
将具有相同哈希地址的记录链成一个单链表, m m m个哈希地址就设 m m m个单链表,然后用一个数组将 m m m个单链表的表头指针存储起来,形成一个动态的结构
优点:插入、删除方便
缺点:占用存储空间多
举个栗子: 设 47 , 7 , 29 , 11 , 16 , 92 , 22 , 8 , 3 , 50 , 37 , 89 设{47,7,29,11,16,92,22,8,3,50,37,89} 设47,7,29,11,16,92,22,8,3,50,37,89的哈希函数为: H a s h ( k e y ) = k e y Hash(key)=key Hash(key)=key m o d mod mod 11 11 11
那么,经过拉链法处理过后的数据应如下图
3.再哈希法
H i = R H i ( k e y ) Hi=RHi(key) Hi=RHi(key) i = 1 , 2 , 3 , … … , k i=1,2,3,……,k i=1,2,3,……,k
优点:不易产生“聚集”
缺点:增加了计算时间
4.建立一个公共溢出区(似乎没什么用,跳过)
H a s h Hash Hash函数的构造方法
1. 直接定址法
取关键字或关键字的某个线性函数值为散列地址,即 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为常数)
由名字就可以看出来该方法简单粗暴,所以一般不适用
2. 除后余数法
即
H
(
K
)
=
K
H(K)=K
H(K)=K
m
o
d
mod
mod
p
p
p
(
p
≤
m
)
(p≤m)
(p≤m)
ps: p一般选取质数,如131,1331,13331(不要问我为什么)
该方法看似简单,实则非常有效,而一个好的模数则会让事半功倍
3. 平方取中法
取关键字平方后的中间几位为哈希函数,因为中间几位与数据的每一位都相关
举个栗子:1314的平方是1726596,那么即可取265作为它的地址
无法评价,毕竟本人从未使用过,有兴趣的可以自己去尝试
4. 数字分析法
即选用关键字的某几位组合成哈希地址,选用的这些数字应具有很高的辨识度,比如说一串数的前四位大致相同,而后四位毫无关联的话,理应优先取后四位
也许等你分析完了,别人已经
A
C
AC
AC了,无语,这方法
5.折叠法
是将关键字按要求的长度分成位数相等的几段,最后一段如不够长可以短些,然后把各段重叠在一起相加并去掉进位,以所得的和作为地址
举个栗子:
42751896
=
427
+
518
+
96
=
1041
42751896=427+518+96=1041
42751896=427+518+96=1041
代码有点难处理,不过还好
6.随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H ( k e y ) = r a n d o m ( k e y ) H(key)=random (key) H(key)=random(key)
总的来说,这六种方法各有千秋,考试时应视不同种情况采用不同的方法
我们需考虑的因素有如下几条:
- 计算哈希函数所需时间;
- 关键字的长度;
- 哈希表的大小;
- 关键字的分布情况;
- 记录的查找频率。
例题讲解
Crazy Search
题目大意:求一个字符串中长度为N的子串(不包含重复)的数量
思路:把每一个子串都
h
a
s
h
hash
hash为
N
C
NC
NC进制的整数
s
s
s,判断
h
a
s
h
[
s
]
hash[s]
hash[s]是否出现过,再进行统计
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e8 + 5;
bool vis[MAXN];
int n , m , ans;
char s[MAXN];
int main() {
cin >> n >> m >> s;
for (int i = 0 ; i <= strlen(s) - n ; i ++) {
int hash = 0 , base = 1;
for (int j = i ; j < i + n ; j ++) {
hash += s[j] * base;
base *= m;
}
if (!vis[hash]) {
ans ++;
vis[hash] = 1;
}
}
printf("%d", ans);
}
兔子与兔子
题目大意:判断两个等长区间里的字符串是否一致
思路:将一个字符串通过一种映射关系,转化为一个整数,通过整数对比来反映字符串关系
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5 , MOD = 13331;
int m , l1 , r1 , l2 , r2;
int hash2[MAXN] , jc[MAXN] = {1};
char s[MAXN];
int main() {
scanf("%s", s + 1);
scanf("%d", &m);
int n = strlen(s + 1);
for (int i = 1 ; i <= n ; i ++) {
jc[i] = jc[i - 1] * 131;
hash2[i] = hash2[i - 1] * 131 + (s[i] - 'a');
}
for (int i = 1 ; i <= m ; i ++) {
scanf("%d %d %d %d", &l1 , &r1 , &l2 , &r2);
int k = hash2[r1] - hash2[l1 - 1] * jc[r1 - l1 + 1];
int k2 = hash2[r2] - hash2[l2 - 1] * jc[r2 - l2 + 1];
if (k == k2) puts("Yes");
else puts("No");
}
}
难题典例
Hash 键值
虽然题目名称是关于 H a s h Hash Hash的,但实际上与 H a s h Hash Hash半毛钱关系都没有
按照正常模拟,我们很容易写出30分的 T L E TLE TLE代码,但拿满分还需一个玄学优化
定义一个状态 s u m [ i ] [ j ] sum[i][j] sum[i][j]为所有% i i i余 j j j的数的总和,所以就可以写出如下代码:
for (int i = 1 ; i <= n ; i ++) {
scanf("%d", &a[i]);
for (int j = 1 ; j <= n ; j ++) {
sum[j][i % j] += a[i];
}
}
不难发现时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),白优化一场,所以这时我们分两类讨论就行了,当
j
∗
j
j * j
j∗j<=n时存在
s
u
m
sum
sum数组里,而大于的部分直接用循环枚举(因为
x
>
s
q
r
t
(
n
)
x>sqrt(n)
x>sqrt(n),所以不会超时),最后输出的时候也分两类情况输出,修改元素时则修改
s
u
m
sum
sum数组与原数组即可
奉上代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int n , q , a[MAXN] , hash[MAXN] , y , opt , x , sum[1005][MAXN];
int main() {
freopen("hash.in" , "r" , stdin);
freopen("hash.out" , "w" , stdout);
scanf("%d %d", &n , &q);
for (int i = 1 ; i <= n ; i ++) {
scanf("%d", &a[i]);
for (int j = 1 ; j * j <= n ; j ++) {
sum[j][i % j] += a[i];
}
}
for (int i = 1 ; i <= q ; i ++) {
scanf("%d %d %d", &opt , &x , &y);
if (opt == 1) {
if (x * x > n) {
int ans = 0;
for (int j = y ; j <= n ; j += x) {
ans += a[j];
}
printf("%d\n", ans);
} else {
printf("%d\n", sum[x][y]);
}
} else {
for (int j = 1 ; j * j <= n ; j ++) {
sum[j][x % j] -= a[x];
sum[j][x % j] += y;
}
a[x] = y;
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探