哈希专题
哈希表定义
哈希表,又称为散列表,是一种根据键来直接访问内存位置的一种数据结构。它通过一个计算键值的函数(散列函数)来将所查询的数据映射到哈希表中的一个位置来查找该位置的内容,从而达到快速查找的目的。(存放记录的数组就称为哈希表)。
- **哈希函数:**每个元素原始值设为x,通过哈希函数h,我们可以得到一个映射值hash(x),记作 k = hash(x) 。
- **散列表:**以每个元素逗得哈希值的哈希值作为下标 ,对应到散列表当中去。
- **冲突:**若不同的元素所映射的哈希值不同,则在散列表中同一位置产生了多个元素的冲突。
- **冲突处理:**根据不同的哈希表设置,有不同的冲突处理方式。
整数哈希
整数哈希共有两种哈希表设置方法,一种叫拉链法,另一种叫开放寻址法。
两种方法所共有的哈希函数在做算法题时都是取模。
h
a
s
h
(
x
)
=
(
x
%
N
+
N
)
%
N
hash(x) = (x\%N+N)\%N
hash(x)=(x%N+N)%N
一般来说,进行取模的数字N都是质数。
拉链法
拉链法的主要逻辑是,把散列表h[]当作一个杆,赶上每个节点以下都挂着一个链表,h[i]记录的就是第i个链表的尾地址,换句话说就是h[]数组是链表尾指针。
而对于每个链表所存储的值都是映射到h[i]位置上的值。
所以显而易见,拉链法处理冲突的逻辑就是,在每一个散列表的尾部插进去这个值。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100003;
/*
!!!
注意还不会手写数组模拟链表的同学去学习这部分内容
*/
int h[N] , e[N] , ne[N];
int idx;
void insert(int x){
int k = (x % N + N) % N ; // 哈希函数
e[idx] = x ; //尾插过程
ne[idx] = h[k] ;
h[k] = idx ++ ;
}
bool Find(int x){
int k = (x % N + N) % N ;
auto it = h[k] ; //遍历链表 找到返回true
while ( it != -1 ){
if(e[it] == x){
return true ;
}
it = ne[it] ;
}
return false ;
}
int main(){
int n;
scanf("%d", &n);
memset(h, -1, sizeof h);
while (n -- ){
string s ;
cin >> s;
int x;
scanf("%d", &x);
if(s == "I"){
insert(x) ;
}
else {
bool tag = Find(x);
if(tag) puts("Yes");
else puts("No");
}
}
return 0;
}
开放寻址法
一次指针
开放寻址法的h[]是用来存储每个元素原本的值,如果产生了冲突,则指针依次地向后移动,直到找到元素的位置或者没有放过元素的位置。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200003 ;
const int inf = 0x3f3f3f3f ;
int h[N] ;
int Find(int x){
int k = (x % N + N) % N ;
int add = 0 ;
while (h[k + add] != inf && h[k + add] != x){ // 找到一个位置要么没有冲突,要么是插入寻找元素本身
add ++ ;
if(k + add == N){
add = -k ;
}
}
int pos = add + k ;
return pos ;
}
int main(){
int n ;
scanf("%d", &n);
memset(h, 0x3f, sizeof h);
while (n -- ){
string s ;
int x ;
cin >> s >> x ;
if(s == "I"){
int pos = Find (x) ;
if(h[pos] == inf ){
h[pos] = x ;
}
}
else {
int pos = Find (x) ;
if(h[pos] == inf ){
puts("No");
}
else {
puts("Yes");
}
}
}
return 0 ;
}
二次指针
二次指针与一次指针地区别在于:一次指针处理冲突时是向后挨个遍历,而二次指针是前后以2的幂指数倍寻找。
如图:
红色的是寻址次数,绿色的是指针所指位置。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200003 ;
const int inf = 0x3f3f3f3f ;
int h[N] ;
int Find(int x){
int k = (x % N + N) % N ;
int add = 0 ;
while (h[k + add] != inf && h[k + add] != x){
if(add == 0){
add = 1 ;
continue ;
}
else if(add < 0){
add *= 2;
}
add = -add ;
}
int pos = add + k ;
return pos ;
}
int main(){
int n ;
scanf("%d", &n);
memset(h, 0x3f, sizeof h);
while (n -- ){
string s ;
int x ;
cin >> s >> x ;
if(s == "I"){
int pos = Find (x) ;
if(h[pos] == inf ){
h[pos] = x ;
}
}
else {
int pos = Find (x) ;
if(h[pos] == inf ){
puts("No");
}
else {
puts("Yes");
}
}
}
return 0 ;
}
要注意两种开放寻址法都要把h[]数组的开辟到数据范围的两至三倍
字符串哈希
字符串哈希主要解决子串是否相等的问题。主要思路以后更新。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100005 ;
const int P = 131 ;
int h[N] , p[N];
char str[N];
int get(int r , int l){
return h[r] - h[l - 1] * p[r - l + 1] ;
}
int main(){
int n ,m;
cin >> n >> m ;
scanf("%s",str + 1) ;
p[0] = 1 ;
for(int i = 1 ; i <= n ; i ++ ){
p[i] = p[i-1] * P ;
h[i] = h[i-1] * P + str[i] ;
}
while (m -- ){
int l1 , r1 , l2 , r2 ;
cin >> l1 >> r1 >> l2 >> r2 ;
if(get(r1 , l1) == get(r2 , l2)){
puts("Yes");
}
else {
puts("No");
}
}
return 0 ;
}