【算法学习】字符串Hash入门

字符串Hash入门

字符串Hash可以通俗的理解为,把一个字符串转换为一个整数

如果我们通过某种方法,将字符串转换为一个整数,就可以便的确定某个字符串是否重复出现过,这是最简单的字符串Hash应用情景了。

当然也不难想到,如果有不同的两个字符串同时Hash到一个整数,这样就比较麻烦了。我们希望这个映射是一个单射,所以问题就是如何构造这个Hash函数,使得他们成为一个单射。不用担心,接下来的内容正要讲解。

Hash方法

给定一个字符串 S = s 1 s 2 s 3 . . s n ,对字母x,我们规定 i d x ( x ) = x a + 1 。 (当然也可以直接用 s i A S C I I 值)

自然溢出方法

Hash公式

unsigned long long Hash[n]

h a s h [ i ] = h a s h [ i 1 ] p + i d ( s [ i ] )

利用unsigned long long的范围自然溢出,相当于自动对 2 64 1 取模

单Hash方法

Hash公式

h a s h [ i ] = ( h a s h [ i 1 ] ) p + i d x ( s [ i ] )   %   m o d

其中 p m o d 均为质数,且有 p < m o d

对于此种Hash方法,将p和mod尽量取大即可,这种情况下,冲突的概率是很低的。

举例

如取 p = 13 , m o d = 101 ,对字符串 a b c 进行Hash
hash[0] = 1
hash[1] = (hash[0] * 13 + 2) % 101 = 15
hash[2] = (hash[1] * 13 + 3) % 101 = 97

这样,我们就认为字符串 a b c 当做97,即97就是 a b c 的hash值。

双Hash方法

将一个字符串用不同的 m o d hash两次,将这两个结果用一个二元组表示,作为Hash结果。

Hash公式

h a s h 1 [ i ] = ( h a s h 1 [ i 1 ] ) p + i d x ( s [ i ] )   %   m o d 1

h a s h 2 [ i ] = ( h a s h 2 [ i 1 ] ) p + i d x ( s [ i ] )   %   m o d 2

hash结果为 < h a s h 1 [ n ] , h a s h 2 [ n ] >

这种Hash很安全。

获取子串的Hash

如果我们求出一个串的Hash,就可以 O ( 1 ) 求解其子串的Hash值。
我们先以一个具体的例子来理解。

例子

假设有一 | S | = 5 的字符串,设 S i 为第 i 个字符,其中 1 i 5

根据定义分别求出 h a s h [ i ]

h a s h [ 1 ] = s 1
h a s h [ 2 ] = s 1 p + s 2
h a s h [ 3 ] = s 1 p 2 + s 2 p + s 3
h a s h [ 4 ] = s 1 p 3 + s 2 p 2 + s 3 p + s 4
h a s h [ 5 ] = s 1 p 4 + s 2 p 3 + s 3 p 2 + s 4 p + s 5

现在我们想求 s 3 s 4 的hash值,不难得出为 s 3 p + s 4 ,并且从上面观察,如果看 h a s h [ 4 ] h a s h [ 2 ] 并将结果种带有 s 1 , s 2 系数的项全部消掉,就是所求。但是由于 p 的阶数,不能直接消掉,所以问题就转化成,将 h a s h [ 2 ] 乘一个关于 p 的系数,在做差的时候将多余项消除,从而得到结果。

不难发现,对应项系数只差一个 p 2 ,而4 - 3 + 1 = 2(待求hash子串下标相减再加一),这样就不难推导出来此例题的求解式子。

h a s h [ 4 ] h a s h [ 2 ] p 4 2 + 1

至此,通过对上例的归纳,可以得出如下的公式。

公式

若已知一个 | S | = n 的字符串的hash值, h a s h [ i ] , 1 i n ,其子串 s l . . s r , 1 l r n 对应的hash值为:

h a s h = h a s h [ r ] h a s h [ l 1 ] p r l + 1

考虑到 h a s h [ i ] 每次对 p 取模,进一步得到下面的式子:

h a s h = ( h a s h [ r ] h a s h [ l 1 ] p r l + 1 ) % M O D

看起来这个式子人畜无害,但是对于取模运算要谨慎再谨慎,注意到括号里面是减法,即有可能是负数,故做如下的修正:

h a s h = ( ( h a s h [ r ] h a s h [ l 1 ] p r l + 1 ) % M O D + M O D ) % M O D

至此得到求子串hash值公式。

值得一提的是,如果需要反复对子串求解hash值,预处理 p n 次方效果更佳。

字符串Hash的应用

题型一

描述

问题:给两个字符串S1,S2,求S2是否是S1的子串,并求S2在S1中出现的次数

数据范围:1=<|S1|,|S2|<=10000

解法

求出S1和S2的Hash值,并且 n 2 的求解出S1所有子串的Hash值,放入map中,查询即可。复杂度 n 2 l o g n

题型二

描述

问题:给N个单词串,和一个文章串,求每个单词串是否是文章串的子串,并求每个单词在文章中出现的次数。

数据范围:文章串长度:[1,10^5],N个单词串总长:[1,10^6]

解法

设单词串总长为 | S | ,文章串总长为 | A |

此题和第一题做法相同。复杂度 | A | 2 l o g | A | + | S |

题型三

描述

问题:给两个字符串S1,S2,求它们的最长公共子串的长度。

数据范围:1=<|S1|,|S2|<=10^5

解法

将S1的每一个子串都hash成一个整数

将S2的每一个子串都hash成一个整数

两堆整数,相同的配对,并且找到所表示的字符串长度最大的即可。

复杂度: O ( | S 1 | 2 + | S 2 | 2 )

PS:为觉得开数组不保险,所以上面的题一和题二都用的map存,这里我也不知道能不能实现 O ( 1 ) 的存储和查询。

题型四

描述

问题:给一个字符串S,求S的最长回文子串。

比如abcbbabbc的最长回文子串是cbbabbc,bbabb也是回文串,但不是最长的

数据范围: 1=<|S|<=10^5

解法

先求子串长度位奇数的,再求偶数的。枚举回文子串的中心位置,然后二分子串的长度,直到找到一个该位置的最长回文子串,不断维护长度最大值即可。

复杂度: O ( | S | l o g | S | )

Hash素数的选取

为了防止冲突,要选择合适的素数,像1e9+7,1e9+9的一些素数,出题人一般会卡一下下,所以尽量选择其他的素数,防止被卡。下面是一些可供选择的素数。
上界和下界指的是离素数最近的 2 n 的值。

lwr(下界) upr(上界) %err(冲突率) prime(素数)
2 5 2 6 10.416667 53
2 6 2 7 1.0416670 97
2 7 2 8 0.520833 193
2 8 2 9 1.302083 389
2 9 2 10 0.130208 769
2 10 2 11 0.455729 1543
2 11 2 12 0.227865 3079
2 12 2 13 0.113932 6151
2 13 2 14 0.008138 12289
2 14 2 15 0.069173 24593
2 15 2 16 0.010173 49157
2 16 2 17 0.013224 98317
2 17 2 18 0.002543 196613
2 18 2 19 0.006358 393241
2 19 2 20 0.000128 786433
2 20 2 21 0.000318 1572869
2 21 2 22 0.000350 3145739
2 22 2 23 0.000207 6291469
2 23 2 24 0.000040 12582917
2 24 2 25 0.000075 25165843
2 25 2 26 0.000010 50331653
2 26 2 27 0.000023 100663319
2 27 2 28 0.000009 201326611
2 28 2 29 0.000001 402653189
2 29 2 30 0.000011 805306457
2 30 2 31 0.000000 1610612741

来源 :http://planetmath.org/goodhashtableprimes

猜你喜欢

转载自blog.csdn.net/pengwill97/article/details/80879387