哈希和字符串哈希

哈希和字符串哈希

什么是哈希?

 哈希是一个将一个**任意大小的数据**映射到一个固定大小值(称为哈希值)的过程,可以通过哈希值作为索引去查询原值。
 举个例子说明,在商店里面,店员可以通过查询商品编码来实现快速实现查看商品价格的过程。这个商品编码就是商品映射出来的值。

使用哈希的时机

可以用来实现在数据规模较大、存储空间较小的时候,通过其映射出来的值,实现快速查询功能

如何实现哈希

在映射过程中,有可能会出现不同的数据,但是映射的值(哈希值)相同,这个情况称为哈希冲突。
想要实现哈希,那就需要解决两个问题;
1.应该如何映射,使得发生冲突的次数小
2.如果发生冲突,如何去解决冲突

1.哈希函数的实现

 映射的过程我们称为哈希函数,假设数据规模为N的话,如果是非负整数,哈希函数一般用映射的数 % N,就会得到[0,N-1]之间的
 数,这个就是哈希值。但如果出现负数的情况,就让(该数 % N +N )% N
 
 如果是负数的情况下,为什么是 (x % N + N) %N呢?
 
 
在x为负数的情况下,在c++中,负数mod一个数可能是负数,可能是0。那么我们开一个数组去通过下标(哈希值)去查找元素
的时候,数组是没有正数的,所以要将其映射到正数。( x % N + N)的操作使得其一定是正数了 ,举个例子说明一下 ,比如说 
-5%3+3 ==2 前面的取模过程得到的数的绝对值一定是比N小的。那既然已经可以保证是整数了,为什么还要modN呢?因为x%N的值
有可能是0,加上N的话那就是N。但我们的目的是想映射到[0,N-1]上,所以呢再mod上N,是一定能保证让哈希值落在[0,N-1]的。

2.解决冲突问题

首先先解决第一个问题,N为多少的时候冲突是会小的,那么根据数学推算,证明了N是质数的时候发生冲突最小。
第二个问题,发生冲突时,如何解决冲突?这里介绍两种方法:拉链法和开放寻址法。

用一道例题来介绍两种方法
在这里插入图片描述
直接插入数组存储,查找的时间复杂度是O(N),但用哈希表的话可以实现O(1).

拉链法的实现
拉链法解决冲突的办法就是开一个槽(数组),如果有哈希值相同的情况,那么用该哈希值的槽作为头,用链表来连接相同哈希值的节点
#include<iostream>
#include<cstring>
using namespace std;
const int N=100003;//这是找到第一个的质数
int h[N];//用来模拟槽
int e[N];//用来存值
int ne[N];//用来存该节点的下一节点,模拟链表
int idx;//起初分配了N个节点,标记使用到哪个节点
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;//找哈希值
	for(int i=h[k];i!=-1;i=ne[i]){//循环,从头结点开始找,一直尾节点,尾节点一开始初始化成-1
	if(e[i]==x) return true;
	}
	return false;
}   
//主函数实现
int main(){
	memset(h,-1,sizeof(h));
	int m;
	cin >> m;
	while(m--){
	char op[2];int x;
	scanf("%s%d",op,&x);
	if(*op=='I'){
	insert(x);
	}
	else{
	if(find(x)) puts("Yes");
	else puts("No");
	}
	}
	return 0;
}
![拉链法的原理图](https://i-blog.csdnimg.cn/direct/b1c2ba6ab68a4a90a83115870b759e39.png)
开放寻址法
开放寻址法的原理是:如果发生冲突的话,那就寻找该哈希值所占的位置的下一个位置,如果这个位置还是有数据,那么就继续找,找到一个还没
存放数据的位置,然后存放。
但需要注意的是,这个方法需要开更大的空间,才能保证在发生冲突之后,一定有个位置能让这个数据存放。而这个空间大小,一般来说,是原来 		
要开辟空间的2-3倍。
所以就一定少不了一个数来标记这个位置有没有被数据占过了,我们也不能取数字范围以内的值。那我们可以取一个范围以外的数来进行对每一个
数进行比较,我们就取0x3f3f3f3f,这个数是比10^9还要大的数,为1061109567。
#include<iostream>
#include<cstring>
using namespace std;
const int N=200003;//这个是找到的质数
int null=0x3f3f3f3f;//作为标记有没有被占过
int h[N];//用来直接存放数据
void insert(int x){
	int k = (x % N + N) % N;
	while(k <= N && h[k] !=null){
		k++;//k在空间内,并且位置被占过的情况下继续寻找
	}
	//走出循环之后,就是找到了
	h[k]=x;
}
bool find(int x){
	int k=(x % N + N) % N;
	while(k<=N && h[k]!=null){
		if(h[k]==x) return true;
		else k++;
	}
	return false;
}
int main(){
	memset(h,0x3f,sizeof(h));
	//memset函数是按字节存储的,如果将每个字节都变成3f的话,对于int4字节而言,就是0x3f3f3f3f就是前面定义的null
	int m;
	cin >> m;
	while(m--){
	char op[2];int x;
	scanf("%s%d",op,&x);
	if(*op=='I'){
	insert(x);
	}
	else{
	if(find(x)) puts("Yes");
	else puts("No");
	}
	}
	return 0;
}


假设数据映射到k下标,发现这个位置已经有数据了,就继续往下找,直到5后面有null,也就是没被占用的地方,将数据存放即可

什么是字符串哈希

字符串哈希就是一个将字符串映射到一个数上的过程,可以通过哈希值,可以用来判断字符串是否相等等问题

字符串哈希的实现

字符串哈希的映射规则是,我们希望能让字符串变成一个P进制的数,再modQ(),举个例子"abc" 可以变成 (97*P^2+98*P^1+99*P^0)%Q
(a的ascll码值是97) 
如果是哈希的话,那很有可能会产生哈希冲突,在数学推算下,我们可以得到,当P=131或者13331,Q=2^64的时候,冲突最小,甚至没有

···
接下来用一道例题来实现一下字符串哈希

在这里插入图片描述

这道题还有一个问题就是如何求出区间字符串的值呢?举个例子,“abcd”怎么求出“cd”的值?取c为l就是左边界,取d为r就是右边界。h[l-1]代表的是“ab”的值,如果能让“ab”变成“ab00",那么用”abcd“的值减去”ab00“的值就能得到”cd“的值。只需要让”ab“的映射值(为h[l–1])*p[r-l+1],再让h[r]减去该值

#include<iostream>
#include<string>
using namespace std;
typedef unsigned long long ULL;
const int N=100010;
//因为mod去2^64冲突最小,又因为unsigned long long的最大值只能存2^64-1以内的值,所以如果发生溢出,就相当于mod了2^64
ULL h[N],p[N];int n,m;
int P=131;
ULL query(int l,int r){
	return h[r]-h[l-1]*p[r-l+1];
}
int main(){
	cin >> n >> m;
	string s;
	cin >> s;
	h[0]=0;p[0]=1;//h[0]可以看做还没有字符,所以映射的值为0,p[i]表示p^i
	for(int i=1;i<=n;i++){
	p[i]=p[i-1]*P;//P^1,P^2,P^3,依次迭代
	h[i]=h[i-1]*P+s[i-1];//假设是"abc",第一次是0*P+97,第二次是97*P+98,第三次是97*P^2+98*P+99,转变成P进制的数
	}
	while(m--){
		int l1,r1,l2,r2;
		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
		if(query(l1,r1)==query(l2,r2)) puts("Yes");
		else puts("No");
	}
	return 0;
}

以上就是我对哈希的理解,作为小白,还需各位前辈多多指教,我乐意接收批评,然后虚心改正!谢谢大家的观看。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值