您的位置:首页 > 其它

String Hashing(字符串哈希)

2015-08-07 13:41 190 查看
判断字符串是否相等可以在常数时间内完成,strcmp()可以在 log(n) 的时间内完成.这看起来很酷,然而计算机科学家会告诉你这被证明是不可能的,但是这对于处理字符串的问题依然是很有用的,让我们来学习一下吧.

设 M 是一个大质数( 109+7 就是一个不错的选择),假设我们有一个随机数生成器 rand(A,B) ,它会生成一个在 [A,B) 区间内的随机数.然后我们要定义一个字符串hash函数 H ,开始时,对于字母表中的每一个字符 c ,都有 H(c)=rand(1,M) .然后我们对于每个字符串都定义有 H(a0...an)=∑k=0n2k⋅H(ck)

然后对于任何一个字符串 S , H(S) 都是 [0,M) 的一个随机数,并且两个不同的字符串有不同的的hash值.

因此,要判断 S1 是否等于 S2 ,你就可以直接比较 H(S1) 和 H(S1) ,错的概率最多是 1/M 。

但是这需要花费 O(|S|) 的时间计算出 H(S) ,我们真的保存了所有的东西么?

假设我们只关心一些字符串的子串 S=a0...an .首先对于每一个在 [0,n] 的 i 计算出 Pi=H(a0...ai) .令 P−1=0 ,又因为 Pi+1=Pi+2i+1⋅H[ai+1] ,计算出所有的P只需要花费 |S| 的时间,于是有 H(ai...aj)=(P(j)−P(i−1))/2i .因为我们要 mod M,除以 2i 可以转化为乘以 2M−1−i ,所以实际上 H(ai...aj)=(P(j)−P(i−1))⋅2M−1−i ,这个时间复杂度是常数的,所以我们可以在常数时间内计算出 S 的任何子串的hash值,因此我们就可以在常数时间内比较S的任何两个子串了.

如果我想比较多个字符串怎么办?

假设你想比较 S1...Sn ,你可以定义 S=S1+...+Sn ,然后你对S再执行与上面相同的操作,现在你就可以在常数时间内比较 S1...Sn 的任意子串和其他任意的子串了.

如果我想…实际的比较?

我们已经做了相等测试,如果我们想实际比较两个字符串怎么办?让我们考虑 S1 和 S2 的实际比较是怎样进行的.我们查找 S1 和 S2 的第一个不同的字符并做比较.所以查找 S1 和 S2 有多少字符匹配就足够了.我们可以二分查找这个数字,因为字符串相等测试可以在常数时间内完成,所以strcmp()现在是 log(n) 的时间了.

你骗人!

你需要在开始时计算并且存储P,而这并没有计算在你的”常数时间”内.

确实如此,但是你首先需要把这些字符串先存起来,你需要花费时间来读入,花费空间来存放.计算P需要1个时间的花费,如果你能承受初始时将这些字符串都读进来的花费,你一定能承受这1个时间的花费.

如果 M 是 109+7 , 计算 2M−1 代价很大.

确实如此,你应该预处理算出所有需要的2的幂,特别的:20,21,...,2|S|−1,2M−1,2M−2,2M−|S| .这是另外2|S|的需要预处理的东西,所以,再说一遍,这很廉价.

这并不是真的有效,它有 1/M 的几率是错的.

确实如此,如果你担心,你可以用和定义H同样的方法来定义 H2 (除了你需要给 H2(c) 一个不同的值),两个字符串相等则需要 H(S1)==H(S2) && H2(S1)==H2(S2) ,这样错的可能性就降到了 1/M2=1/1018 ,这样应该能使你高兴.除非你是Goolge或者是其他什么才会定义H3,H4甚至是H5.

但这还是错的,就算仅有1/1045的几率是错的,这依然是错的.

不,这真的不会的,你知道宇宙射线实际上会使内存上的某些bit位反转?(这是不是很酷?)Intel估计每256MB的内存每个月发生一次会这样的情况?所以宇宙射线选择某个确切的时钟周期并反转了你正在做比较的bit位的几率大约是 1/1024 ,但是你不要到处乱说

“我的程序因为宇宙射线而不是很对.”你甚至从未考虑到宇宙射线,那又为什么去把时间浪费在担心这 1/1045 的几率的失败?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  string hash