哈希是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的信息摘要的函数。
关于哈希表学到了两种运用方式
一:存储结构,存储结构又有开放寻址法和拉链法两种方式
二:字符串哈希方式。
这些用法的本质都是将一个大的值域映射到一个小的空间。
在一中,就表现为对一个数求余来缩小值域。对选择这个数也有讲究,选择质数且离二的倍数较远的树,会降低哈希冲突的概率,下面将这个数表现为N;
1.开放寻址法
开放寻址法是只采用一维数组来存储的方法,若遇到哈希冲突则向后移一位。
其中核心代码为如何查找一个数的位置,如下
const int null=0x3f3f3f;
int find(int x)
{
int k=(x%N+N)%N;
while(h[k]!=null&&h[k]!=x)
{
k++;
if(k==N) k=0;//返回开头查找
}
return k;
}//若有返回下标,没有则返回应该存储的位置;
关于如何插入,若未找到查找函数会返回这个数应该存储的位置,以此存入就好了
2.拉链法
拉链法顾名思义采用了链表+一维数组的i形式,和开放寻址法不同的是,拉链法在冲突的情况下选择开一条链表而不是向后推移。
核心代码有查找和插入两段
void insert(int x)//插入
{
int k=(x%N+N)%N;//映射到0到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])
if(e[i]==x)
return true;
return false;
}
以上两种方法的删除都不需要真正的删除,只需要做一个特殊的标记即可
二.字符串前缀哈希法
即将一个字符串转化成一个整数,并保证字符串不同,得到的哈希值不同,这样就可以用来判断一个该字串是否重复出现过。
那么如何将一个字符串转化成整数呢
我们假设它为一个b进制数
例如一个字符串abc,它的值就为a*+b*+c*;
注意点:
1、尽量不要映射成0,因为假如一个字母A映射成0,那么AA或者AAA它们的映射也都是0
2、b的进制我们习惯取131或者13331,Q=2^64,这样我们就可以假定不会发生冲突。
核心代码如下
typedef unsigned long long ULL;//取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
ULL h[N], p[N];//h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];//这里的p[r-l+1]相当于乘上p的r-l+1次方
}//计算字串的哈希值
最后关于哈希表的应用方面
我认为哈希表可以作为一种高效的存储和查找的手段。