摘要:有没有办法做到这一点?这就是我的意思:假设我有一个 unsigned int 数字。然后我将它乘以几次(并且有溢出,这是预期的)。那么是否可以“恢复”原始值?
详情:
关于Rabin-Karp rolling hash的全部内容。我需要做的是:我有一个长字符串的哈希 - 例如:“abcd”。然后我有一个更短的子串的哈希 - 例如“cd”。如何用O(1)计算“ab”哈希值,使用两个给定的哈希值?
我现在所拥有的算法:
p ^ len( "cd" )
划分“abcd”哈希,其中p
是基数(素数)。 所以这是:
a * p ^ 3 + b * p ^ 2 + c * p ^ 1 + d * p ^ 0
- abcd
c * p ^ 1 + d * p ^ 0
- cd
ab 获取:
( ( a * p ^ 3 + b * p ^ 2 + c * p ^ 1 + d * p ^ 0 ) - ( c * p ^ 1 + d * p ^ 0 ) ) / ( p ^ 2 ) = a * p ^ 1 + b * p ^ 0
这是有效的,如果我没有溢出(如果p
是小数字)。但如果不是 - 它不起作用。
有什么伎俩吗?
P.S。 c++
标签是由于数字的溢出,因为它是特定的(并且与python,scheme或sth不同)
答案 0 :(得分:5)
不知道溢出部分,但有一种方法可以取回原始值。
中国剩余定理帮助很大。我们打电话给h = abcd - cd
。 G是值h
,没有溢出G = h + k*2^32
,假设溢出只是%2^32
。因此ab = G / p^2
。
G = h (mod 2^32)
G = 0 (mod p^2)
如果p ^ 2和2 ^ 32是互质的。 Chinese Remainder Theorem上的此页面为我们提供了
G = h * b * p^2 (mod 2^32 * p^2)
其中b
是模数乘法逆p ^ 2模2 ^ 32,b * p^2 = 1 (mod 2^32)
。在您计算G
后,只需按p^2
除以ab
。
我希望我没有犯任何错误......
答案 1 :(得分:3)
扩展欧几里得算法是一个很好的解决方案,但它太复杂,难以实现。有一个更好的。
还有另一种方法可以做到这一点(感谢我的朋友(:)
当m
和a
是互质时,使用欧拉定理的wikipedia - 模乘法逆中有一篇很好的文章:
其中φ(m)
为Euler's totient function。
就我而言,m
(模数)是散列类型的大小 - 2^32
,2^64
等等(在我的情况下为64位)。
嗯,这意味着,我们应该只找到φ(m)
的值。但是考虑一下 - m == 2 ^ 64
这样,我们就可以保证m
将是所有奇数的
另外,我们知道m
将是未签名的,否则我们会遇到一些问题。比这更让我们有机会这样做:
hash_t x = -1;
x /= 2;
hash_t a_reverse = fast_pow( a, x );
好吧,关于64位数字,x
真是一个大数字(19位:9 223 372 036 854 775 807
),但是fast_pow
非常快,我们可以缓存反向数字,以防我们需要多个查询。
fast_pow
是一个众所周知的算法:
hash_t fast_pow( hash_t source, hash_t pow )
{
if( 0 == pow )
{
return 1;
}
if( 0 != pow % 2 )
{
return source * fast_pow( source, pow - 1 );
}
else
{
return fast_pow( source * source, pow / 2 );
}
}
增加:例如:
hash_t base = 2305843009213693951; // 9th mersenne prime
hash_t x = 1234567890987654321;
x *= fast_pow( base, 123456789 ); // x * ( base ^ 123456789 )
hash_t y = -1;
y /= 2;
hash_t base_reverse = fast_pow( base, y );
x *= fast_pow( base_reverse, 123456789 ); // x * ( base_reverse ^ 123456789 )
assert( x == 1234567890987654321 ) ;
完美且非常快。
答案 2 :(得分:1)
您应该使用无符号整数来获得定义的溢出行为(模2 ^ N)。有符号整数溢出未定义。
此外,不是划分,而是应该乘以p的乘法逆与模相应的值。例如,如果p = 3并且您的哈希值是8位,则乘以171,因为171 * 3 = 513 = 2 * 256 + 1。如果p和模数值是相对素数,则存在乘法逆。
答案 3 :(得分:1)
这里只是一个部分的答案:我认为使用无符号整数是非 。您可以使用one's complement。
但请注意,这将为-0和+0提供单独的表示,并且您可能需要手动编码算术运算。
某些处理器指令与整数表示无关,但不是全部。
答案 4 :(得分:1)
你有一个* b = c mod 2 ^ 32(或者根据你的哈希方式修改其他东西)。如果你能找到d这样b * d = 1 mod 2 ^ 32(或mod什么)那么你可以计算a * b * d = a 并检索一个。如果gcd(b,mod 2 ^ 32)= 1,那么你可以使用http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm找到x和y,使得b * x + 2 ^ 32 * y = 1,或者 b * x = 1 - y * 2 ^ 32,或 b * x = 1 mod 2 ^ 32,所以x是你想要乘以的数字。
答案 5 :(得分:0)
所以溢出实际上只是你的编译器对你很好; C / ++标准实际上表明溢出是未定义的行为。因此,一旦你溢出,实际上你无能为力,因为你的程序不再是确定性的。
您可能需要重新考虑算法,或采用模运算/减法来修复算法。