概念:通过一个hash函数建立值与存储地址的关系
原则:
- 开小数组+冲突解决
- 冲突越少,用时越少;可通过调整余数或优质的hash算法尽量使hash值分散,减少碰撞
hash算法的构成:
hash函数的初始化
构造hash函数:
典型的函数包括除余法 H ( k ) = ( k ) m o d p H(k)=(k)modp H(k)=(k)modp、elfhash等等
冲突解决方法:
(常用技术)线性探测再散列技术:当 ( k ) m o d p (k)modp (k)modp下标对应有元素时,依次探测 ( k + i ) m o d p (k+i)modp (k+i)modp下标位置直至数组为空,再将元素放入。
再开数组辅助定位:
在存储后,使用hash函数定位某个元素时,不仅需要一个数组确定某个下标位置是否存在元素,当某个位置有元素时还需要一个数组存储该位置的原本元素方便精确校验是否为该元素。
拓展hash:
- 字符串哈希
使用elfhash算法、bkdrHash算法尽可能的减少存储时的,其冲突,其他操作与经典hash无区别
bkdrhash:
h a s h ( s ) = ( ∑ i = 1 l s [ i ] × b a s e l − i ) m o d M hash(s)=(\sum^l_{i=1} s[i]\times base^{l-i})modM hash(s)=(∑i=1ls[i]×basel−i)modM或者
h a s h ( s ) = ( ∑ i = 1 l s [ i ] × b a s e i − 1 ) m o d M hash(s)=(\sum^l_{i=1} s[i]\times base^{i-1})modM hash(s)=(∑i=1ls[i]×basei−1)modM
- 自然溢出法
利用C++中的unsigned long long 数据类型声明哈希值,就相当于使用大数的自然溢出为哈希值模 2 64 − 1 2^{64}-1 264−1这个数。
#include<bits/stdc++.h>
#define maxn 100000
#define INF 0x3f3f3f3f
#define ull unsigned long long
using namespace std;
int N,base=233,cnt=1;
char s[2000];
ull h[maxn];
ull hashhit(char *str)
{
ull res=0;
int len=strlen(str);
for(int i=0;i<len;i++)
res=(res*base+(ull)str[i]);//自然溢出
return res&0x7fffffff;//相当于将位数控制在int最大位数内
}
int main()
{
scanf("%d",&N);
for(int i=0;i<N;i++)
{
scanf("%s",s);
h[i]=hashhit(s);
}
sort(h,h+N);
for(int i=0;i<N-1;i++)
if(h[i]!=h[i+1])
cnt++;
printf("%d",cnt);
system("pause");
}
- 单哈希
即使用一个mod值和base值计算哈希值 - 双哈希
即使用两个mod值和base值确定两个哈希值,从而确定一个字符串。代码:
#include<bits/stdc++.h>//自然溢出双哈希,bkrd算法
#define maxn 100000
#define INF 0x3f3f3f3f
#define ull unsigned long long
using namespace std;
int N,base1=233,cnt=1,base2=2333;
char s[2000];
struct dhash{
ull x;
ull y;
}h[maxn];
ull hashhit1(char *str)
{
ull res=0;
int len=strlen(str);
for(int i=0;i<len;i++)
res=(res*base1+(ull)str[i]);//自然溢出
return res&0x7fffffff;//相当于将位数控制在int最大位数内
}
ull hashhit2(char *str)
{
ull res=0;
int len=strlen(str);
for(int i=0;i<len;i++)
res=(res*base2+(ull)str[i]);//自然溢出
return res&0x7fffffff;//相当于将位数控制在int最大位数内
}
int cmp(dhash a,dhash b)
{
if(a.x!=b.x)
return a.x<b.x;
else
return a.y<b.y;
}
int main()
{
scanf("%d",&N);
for(int i=0;i<N;i++)
{
scanf("%s",s);
h[i].x=hashhit1(s);
h[i].y=hashhit2(s);
}
sort(h,h+N,cmp);
for(int i=0;i<N-1;i++)
if(h[i].x!=h[i+1].x||h[i].y!=h[i+1].y)
cnt++;
printf("%d",cnt);
system("pause");
}
- 全排列哈希(康托展开)
介绍:
康托展开是一种能建立全排列与自然数之间的双射关系的方法;具体算法如下:
自然数为X, X = a n ( n − 1 ) ! + a n − 1 ( n − 2 ) ! + . . . . + a 1 0 ! X=a_n(n-1)!+a_{n-1}(n-2)!+....+a_10! X=an(n−1)!+an−1(n−2)!+....+a10!其中系数 a i a_i ai为全排列中所有种类的数字剔除已经出现的数字的集合,集合内小于第i位数的数字的个数。如54123,从左到右依次计算系数a,此时集合内有{1,2,3,4,5}这些数字,小于5的数字有4个,则 a 5 = 4 a_5=4 a5=4,同时剔除已经出现的数字5,集合内还剩{1,2,3,4},下一个数字为4,以此类推 a 4 = 3 , a 3 = 0 , a 2 = 0 , a 1 = 0 a_4=3,a_3=0,a_2=0,a_1=0 a4=3,a3=0,a2=0,a1=0,综上该数对应114因此,将全排列通过康托展开映射到hash表是hash算法中效率最高的选择。