【问题标题】:Rabin-Karp Hashcode is too bigRabin-Karp 哈希码太大
【发布时间】:2012-09-17 02:34:15
【问题描述】:

如何处理滚动哈希Rabin-Karp算法中的大哈希码值?我使用模运算来避免负数,但是当哈希码超过我的模数(N = 83559671)时会出现问题。我将我的基数设置为素数(计算哈希码的数字)以及模数(非常大),但它不适用于长字符串。任何人都可以看到问题吗?

这是我的代码。

   public static void main(String [] args){

       int P = 13;         // base
       long M = 83559671;
       long iHash = 0;    
       String word = "abcbadccaaaabbbb";
       int WINDOW = 9;

       for(int i = 0; i < WINDOW; i++){
            iHash = int_mod(int_mod(iHash*P, M) + word[i], M);
       }

       for(int i = WINDOW; i < word.length; i++){
            iHash = int_mod(iHash - word[i-WINDOW] * get_pow(P, WINDOW-1, M), M);
            iHash = int_mod(iHash * P, M);
            iHash = int_mod(iHash + word[i], M);
       }

   }
   public static long get_pow(int p, int t, long M){
        long a = 1;
        for(int i = 0 ; i < t; i++){
              a = int_mod(a * p, M);
        }
        return a;
   }

   public static long int_mod(long a, long b){
        return (a % b+ b) % b;
   }

问题是当我有任何字符串的长度超过 8 时,字符串的哈希码超过了模数 83559671,当我进行比较时会导致错误的答案。任何较短的字符串都可以正常工作。

【问题讨论】:

  • 为什么要避免负数?如果必须,将它们视为无符号,但我认为除了不可避免的 2^32 之外,您根本不需要做任何模数。
  • 我正在使用 Java,我认为 Java 不支持 unsigned ?如果我没记错的话,或者你的意思是 BigInteger 吗?
  • Java 的整数通常是有符号的,但您可以将它们视为无符号的,并且大多数情况实际上都是一样的。
  • 我的意思是我可以使用它,但如果你有一个负哈希码,它会不会不正确?只需将您从该负数中减去最左边的字符并添加一个新字符即可。最终不会导致不同的哈希码吗?
  • 不,不是。减法、加法和乘法都适用于有符号整数,就像它们适用于无符号整数一样,都是 mod 2^32。

标签: java algorithm hash string-matching


【解决方案1】:

你根本不需要做模数。这是一个演示:

public class Foo {
  private static int hash(String s) {
    int hash = 0;
    for (int i = 0; i < s.length(); i++) {
      hash *= 31;
      hash += s.charAt(i);
    }
    return hash;
  }

  public static void main(String[] args) {
    String s1 = "abcdefghij";
    String s2 = s1.substring(1) + "k";
    int pow = 1;
    for (int i = 0; i < s1.length(); i++) {
      pow *= 31;
    }
    System.out.printf("hash(%s) = %d%n", s1, hash(s1));
    System.out.printf("hash(%s) = %d%n31 * hash(%s) - (31^%d * %s) + %s = %s%n",
        s2,
        hash(s2),
        s1,
        s1.length(),
        s1.charAt(0),
        s2.charAt(s2.length() - 1),
        31 * hash(s1) - (pow * s1.charAt(0)) + s2.charAt(s2.length() - 1));
  }
}

这个(正确)打印出来:

hash(abcdefghij) = -634317659
hash(bcdefghijk) = 21611845
31 * hash(abcdefghij) - (31^10 * a) + k = 21611845

【讨论】:

  • int 是否会溢出?我们可以使用 long 吗? int 是否足够好?
  • 我们不必关心溢出。溢出是很好。这就是重点。 (本质上,溢出等效于 2^32 的模数,因此可以解决。)
  • 你说得对,它确实会循环回正数,所以我们在任何情况下都不需要切换到 long 吗?现在似乎 mod 对于一个大数字来说不是必需的,但如果我们使用它会有帮助吗?
  • 这并没有真正的帮助,但它会使代码更混乱,更容易搞砸。只是不要打扰。
  • 好帖子!愚蠢的语义注释,但如果是我,我会将pow 重命名为mult,因为它的作用是乘数,而不是幂。 ;-) 对于从这篇文章中学习的人来说,这可能会有所作为。
【解决方案2】:

你为什么不把你的字符串当作多项式呢?假设您有一个长度为n 的字符串S。现在看看下面的函数:F(x) = S[0]*x^(n-1) + S[1]*x^(n-2) + ... + S[i]*x^(n-i-1) + ... + S[n - 2]*x + S[n-1]。如果您尝试计算F(P),会发生什么情况,其中P 是您的代码sn-p 的基础?好吧,你会得到字符串S 的Rabin-Karp 哈希值。但是由于F(x) 是一个多项式,我们可以使用Horner's rule 来计算F(P)。结果值可能非常大,因此我们使用模运算:

static final long M = 83559671;
static final int Base = 13;

static long hash(String s, int from, int to) {
    int iHash = 0;
    for(int i = from; i < to; i++) {
        iHash *= Base;
        iHash += s.charAt(i);
        iHash %= M;
    }
    return iHash;
}

您可以使用此函数获取要在文本中找到的字符串的哈希值。而对于文本中的初始窗口。然后你可以移动窗口并重新计算哈希:

static void find(String pattern, String text) {
    if(text.length() < pattern.length()) return;
    int len = pattern.length();
    long ph = hash(pattern, 0, len);
    long h = hash(text, 0, len);
    long basePower = mpow(Base, len);

    if(h == ph) System.out.println("match at 0");
    for(int i = len; i < text.length(); i++) {
        h *= Base;
        h += text.charAt(i);
        h -= basePower * text.charAt(i - len);
        h = mod(h);
        if(h == ph) System.out.println("match at " + (i - len + 1));
    }
}

static long mod(long a) {
    a %= M;
    if(a < 0) {
        a += M;
    }
    return a;
}

static long mpow(long x, int k) {
    long result = 1;
    for(; k > 0; k >>= 1) {
        if(k % 2 == 1) {
            result = mod(result * x);
        }
        x = mod(x * x);
    }
    return result;
}

public static void main(String[] args) {
    find("abracadabra", "abracadabracadabra");
}

有关此方法的更多信息,我建议您参考CLRS。

【讨论】:

  • 我真的不明白...你不需要每次移动时都取出最左边的字符吗?
  • 很像我的,不是吗?如果 h *= Base 变为负数会怎样? Java long 也已签名,因此如果您的模式字符串很长并且您的主要基数很大,那么您将遇到负哈希码问题,对吗?这就是我的问题
  • 嗯,什么是负数模M?说,-x?它是 M - (x mod M)。我在mod 函数中处理这种情况
猜你喜欢
  • 1970-01-01
  • 2013-12-23
  • 2021-05-26
  • 2017-07-27
  • 2021-07-03
  • 2023-03-09
  • 2012-01-18
  • 1970-01-01
  • 2018-11-22
相关资源
最近更新 更多