我指的是Rabin Karp Wikipedia article on Hash use.
在示例中,字符串"hi"
使用素数101
进行哈希处理。
hash("hi")= ASCII("h")*101^1+ASCII("i")*101^0 = 10609
这种算法是否可以在Java或C#中实际使用,其中long的最大值为9,223,372,036,854,775,807
?天真地,对我来说,似乎哈希值呈指数增长,并且具有足够大的N(字符串长度)将导致long
类型的溢出。例如,假设我的字符串输入中有65个字符用于哈希?
这是正确的,还是有永远不需要溢出的实现方法(我可以想象一些懒惰的评估只能将ascii和单位存储在主要基础中)?
答案 0 :(得分:1)
如果您的目标是一种仅包含“小”号的存储,则 但可以比较总和:
你可以简单地将其视为101号码系统,
像10 =十进制,16 =十六进制。等等。
IE浏览器。
a)你必须存储一组{ascii值,它是101-power}
(没有可能具有相同功率的多个条目)。
b)从字符串创建数据时,
值> 101必须传播(这是正确的词?)到下一个幂。
示例1:
“a”是97 * 101 ^ 0
(琐碎的)
例2:
“g”是1 * 101 ^ 1 + 2 * 101 ^ 0
因为g是103. 103> = 101即。 101 ^ 101只占103%101
(模数,除法的余数)
和(int)(103/101)为下一个权力。
(如果ascii numers可能更高或者素数低于101
(int)(103/101)也有可能超过主要数字
在这种情况下,它将继续填充^ 2,依此类推,直到值较小为止
比素数)
例3:
“ag”是98 * 101 ^ 1 + 2 * 101 ^ 0
与上述相比,由于a增加了97 * 101 ^ 1。
等等...
在不计算全额的情况下进行比较,
只需将每个电源的功率值相互比较即可
如果所有“功率值”相同则相等。
附注:请注意,^不是C#和Java等语言中的取幂。
答案 1 :(得分:1)
hash("hi")= ASCII("h")*101^1+ASCII("i")*101^0 = 10609
这只是事实的一半。实际上,如果你实际上计算了值s_0 * p^0 + s_1 * p^1 + ... + s_n * p^n
,结果将是一个数字,其表示与字符串本身一样长,所以你没有获得任何东西。所以你实际做的是计算
(s_0 * p^0 + s_1 * p^1 + ... + s_n * p^n) mod M
其中M
相当小。因此,您的哈希值将始终小于M
。
所以你在实践中做的是选择M = 2^64
并利用无符号整数溢出在大多数编程语言中定义良好的事实。事实上,Java,C ++和C#中64位整数的乘法和加法相当于乘法和加法模2^64
。
使用2^64
作为模数不一定是明智的选择。实际上,您可以轻松地构造一个包含大量碰撞的字符串,从而引发Rabin-Karp的最坏情况行为,Ω(n * m)
匹配而不是O(n + m)
。
最好使用大质数作为模量并获得更好的抗碰撞性。通常没有这样做的原因是性能:我们需要明确地使用模块化缩减(添加% M
)到每个加法和乘法。更糟糕的是,我们甚至不能再使用内置乘法,因为如果M > 2^32
它可能会溢出。所以我们需要一个自定义MultiplyMod
函数,它必然比机器级乘法慢很多。
这是正确的,还是有永远不需要溢出的实现方法(我可以想象一些懒惰的评估只能将ascii和单位存储在主要基础中)?
正如我已经提到的,如果你不减少使用模数,你的哈希值将增长到与字符串本身一样大,从而使得首先使用哈希函数变得无用。所以是的,使用受控溢出模2^64
是正确的,如果我们不手动减少,甚至是必要的。