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