数据结构与算法之散列
其实在之前的算法练习题中已经使用到了散列(hash)这一思想,那就是以空间换时间。
举例:现有整数集合M,还有一个输入的整数集合N,且M,N均小于等于10 的5次方。那么,我想知道集合N中的哪些整数出现在了集合M中?
分析:一般的思路肯定是拿两个集合的数一个个比较,即用两个嵌套的循环实现。则此种的算法时间复杂度为O(n*m)。那么我们是否有办法使得时间复杂度从O(n+m)呢?即不使用嵌套的循环。办法就是空间换时间。
我们先初始化一个数组hash[],长度至少为集合M、N中最大的整数值。
代码如下:
#include<cstring>
int main()
{
const int maxn = 100001;
int hash[maxn];
memset(hash,0,sizeof(hash));
int n;//集合N中的整数个数
int m;//集合M中的整数个数
int x;
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++){
scanf("%d",&x);
hash[x]+=1;//将输入的整数作为数组的下标。
}
for(int i=0;i<m;i++){
scanf("%d",&x);
printf("%d ",hash[x]);
}
return 0;
}
通过以上的代码我们成功的将时间复杂度降为O(n)级别,我们现在进一步的思考,当整数是非常大的话,比如是
1
0
9
10^{9}
109,此时如果仍将其当做数组的下标的话,那么我们需要开一个很大的数组,显然此时的空间复杂度是很大的。所以我们可以取余数,即%mod,mod取一个很大的数,就可以对数进行缩小。如果集合中不再是整数,而是一个个的字符串,我们可以将其变换为数值。
像上面通过对输入的数或字符串进行缩小或变换成数值,就是散列。散列即:将元素通过一个函数转换为整数,使得该整数可以尽量的唯一的代表该元素。这个变换函数就称为散列函数H。
如果元素在转换前是key,则转换后就是H(key)。
除留取余法:H(key)=key%mod。这样转换后的数值均在mod内。一般mod大于数组的范围。一般取mod为一个素数。避免转换前不相等的两数在转换后成相等的。
如果转换后有比较多重合的数,我们用三种方法处理。
1,线性探查法:当key1!=key2,而H(key1)=H(key2),那么我们将key2向后挪一个位置,即H(key2)=H(key2)+1,如果这个位置仍被占则我们继续向后,如果超过了数组的范围,那么我们就从头开始继续,直到找到一个新位置。
2,平方探查法:我们使用线性探查法会出现“扎堆”的现行,即一连串的位置都被占了。那么我们我可以用平方探查法,H(key2)=(H(key2)+
k
2
k^{2}
k2)%length,length是数组的长度。
3,链地址法,当key1!=key2,而H(key1)=H(key2)时,我们不在对H(key2)的数值进行变换,而是将key1,key2连接成一个单链表。
通过以上的内容,我们对于整数的散列有了基础的了解。那对于不是整数时,怎样进行散列呢?比如我们如何对二维的坐标进行散列呢?为了避免key1!=key2,而H(key1)=H(key2)这种情况,我们可以将二维坐标这样散列:(x,y)=x*range+y。即我们将其散列成一个整数。同样我们对于字符串也可散列成整数。
1,当字符串全由大写的字母组成时,我们将字符看做是26进制将其化为10进制的整数:
int str_hash_int_1 (char str[],int length){
int t=0;
for(int i=0;i<length;i++){
t=t*26+(str[i]-"A")
}
return t;
}
2,当字符串有大小写字母组成时:
int str_hash_int_2(char str[],int length){
int t=0;
for (int i=0;i<length;i++){
if(str[i]<="Z"&&str[i]>="A"){
t=t*52+(str[i]-"A")
}
else {
t=t*52+(str[i]-"a")+26
}
}
reuturn t;
}
3,当字符混合着大小写字母和数字时:
int str_hash_int_3(char str[],int length){
int t=0;
for(int i=0;i<length;i++){
if (str[i]<="Z"&&str[i]>="A" ){
t=t*62+(str[i]-"A")
}
if (str[i]<="z"&&str[i]>="a" ){
t=t*62+(str[i]-"a")+26
}
if (str[i]<="Z"&&str[i]>="A" ){
t=t*62+(str[i]-"1")+52
}
}
return t;
}