学习:数据结构哈希

 哈希是一种既靠谱又不靠谱的储存,查找方法。哈希更多来讲是一种不可逆性的映射,因此具有很强的对应性,方便了查找的同时,还方便了其他一些奇葩算法操作。

哈希?


哈希,也叫作hash或散列。

若一个数的值是x,那么某一种对应关系f叫做哈希变换,通过哈希变换,就可以由x得到变换后的值f(x),这就叫做哈希。

哈希的某种对应关系f,在变换之后原来的某些性质可能发生改变,但也可能保持不变。

比如说x*y=z,这是一种乘法性质,若某种对应关系是f(a)=a%mod,那么x,y,z在经过变换之后得到的x',y',z',仍然保持x'*y'=z'

但是,若对应关系式f(a)=a+c(c为一个常数),那么x,y,z在变换之后,就不具有x'*y'=z'的性质了。

所以

  为了方便计算等运用,我们还要找到一种合适的对应关系,进行哈希变换,可以达到繁化简的奇效。

那么哈希有啥用捏?

比如说,我们要验证x*y是否等于z,然而x,y和z都是很大的数,很明显直接比较肯定会爆数据范围,于是我们可以将x,y,z进行f(a)=a%mod的哈希变换,变成相对较小的x',y',z',然后再验证x'*y'是否等于z'即可(当然,这样子可能有风险,后面我们再继续讨论风险问题)


哈希表


哈希表则是哈希的一种产物。

哈希表M如同其他表一样,存有一系列的数据,但是查找哈希表中某一个数据的位置,是可以做到O(1)复杂度,因为哈希表中的某一数据与此数据在表中的位置具有很强的关系

假如有一个值x,那么存在一种关系f,那么x在哈希表中的位置就是f(x)

下面是创建一个哈希表的样例:

  有一堆数据14,29,6,74,25,101,40

             
0 1 2 3 4 5 6

 

 

 

  将数据全部模上表长7,余数即为此数据在表中的位置,最后得到

14 29 74 101 25 40 6
0 1 2 3 4 5 6

 

 

 

  于是,每次要查询一个值,只用将此值mod 7,然后检测表中此位置是否为这个值,若是,则表中存在此值,否则不存在

 

当然,哈希表还可以让两个值产生对应关系,比如:

原值(x 14 29 74 101 25 40 6
变换后值(x' 2 90 19 0 46 63 13

 

 

可以发现x'=x!%101

 

当然,如果两个数经过对应关系之后,可能变成同一个数,这样哈希表就会产生二义性,也叫哈希冲突,后面就会讲解如果降低哈希冲突的概率


哈希冲突


前言

我们想要的哈希,是为了实现一对一的关系,才能保证唯一性,但是在有的情况下,两个不同的数经过哈希变换之后,可能出现多对一的情况,于是我们就要提高哈希变换的复杂度,从而实现唯一性

比如上述的哈希表中,如果两个数模7得到的是同一个数怎么办?

线性探测法

线性探测法是比较妥协的方法,如果经过哈希变换后在表中对应的位置已经被占据,那么我们就沿着表往后找,直到找到一个空单元,就占用此空单元

特点:这样做虽然也能完美解决冲突,但是如果在表中很长的一部分区域没有空单元,那么在一个个寻找空单元的过程中需要耗费大量时间,且在查询值的时候还可能需要查询整个表,失去了哈希原有的低复杂度特点

链地址法

链地址法,即表中存的每个链表的头结点,一旦有两个值经过哈希变换后对应表中的同一位置,那么每个值就以节点的形式接在当前位置头结点的后面

如数据:

  8,37,25,7,50,40,39

这些数据模7放入表中用链地址法解决冲突后如下所示:

 

特点:链地址法也能完美解决哈希冲突,但是空间利用率不高,且如果某一个链表很长,在查询过程中可能需要查找整个链表时间复杂度也变得很高

多哈希法

多哈希法一般不适用于解决哈希表储存冲突,反而,多哈希法能很好地表示某个数就是这个数它自己如果两个数模上一个数后的值可能相同,那么就让这两个数同时模上多个数,得到多个值,将这个多个值放在数组中,得到这两个数的两个特征数组。如果两个数的特征数组内的数全部相同,才能证明这两个数相同。

比如7和14模上7都等于0,但是7模上2等于1,14模上2等于0,所以7的特征数组为{0,1},14的特征数组为{0,0},两个特征数组不相同,所以7不等于14。

特点:多哈希法经常运用于算法题中,多哈希法虽然需要计算多次,但是空间和时间复杂度都不是很高,且能使哈希冲突降的很低,同时,特征数组还能起到大化小的妙用。


字符串哈希


字符串哈希普遍用的一种方法:

  首先选一个数x,你自己定义的,我觉得最好是质数。假设字符串为s[],然后定义一个unsigned long long类型数组prefix[]prefix数组存字符串前缀子串hash值,于是

$$prefix[i]=prefix[i-1]*x+s[i]$$

这种方法可以很好地求出s字符串中任意一个子串的hash值,定义一个数组powx[],且powx[i]=xi,那么以s[i]~s[j]的子串的hash值为:

$$prefix[j]-perfix[i-1]*powx[j-i+1]$$

注意:powx[0]=1,prefix[0]=0,当然可能出现两个不同的子列hash值相同,这时可以用多hash法(即再选一个x值)得出子列的特征数组,判断特征数组是否相同,这样可以将冲突降至最低。


字符串哈希应用


KMP算法

KMP算法用来判断一个字符串是不是另一个字符串的子串(注意子串是连续的,子序列是不连续的)

例题:给定一个长度为$n$的字符串$s$,求另一个长度为$m$($m\le n$)的字符串$t$是不是$s$的字符串。

首先可以求出原字符串$s$的所有长度为$|t|$的子串哈希值,然后求出字符串$t$的哈希值。判断所有长度为$|t|$的子串的哈希值是否存在等于字符串$t$的哈希值

存在,则$t$为$s$的子串,否则不是$s$的子串。

复杂度为$O(nlogn)$,用KMP算法复杂度为$O(n)$

AC自动机

AC自动机用来判断一系列的字符串是不是另一个字符串的子串

例题:给定$t$个长度小于等于$n$的字符串,求这些字符串是不是另一个长度为$m$的字符串$s$的子串。

首先求出字符串$s$的所有子串哈希值,然后求出每一个长度小于等于$n$的字符串哈希值,然后进行判断即可。

复杂度为$O(ntlogn+m^2)$,用AC自动机复杂度为$O(m+nt)$

例题


 1.牛客练习赛48----B-小w的a=b问题:https://blog.csdn.net/weixin_43702895/article/details/94721009

 

原文地址:https://www.cnblogs.com/qiyueliu/p/11140428.html