Hashcode Рабина-Карпа слишком велик

Как бороться с большим значением хеш-кода в алгоритме Рабина-Карпа с переменным хешем? Я использую модульную арифметику, чтобы избежать отрицательного числа, однако есть проблема, когда хеш-код превышает мое число по модулю (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 ответа

Вам не нужно делать модуль вообще. Вот демо:

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

Почему бы вам не рассматривать вашу строку как полином? Предположим, у вас есть строка S длины n, Теперь взглянем на следующую функцию: 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 база из вашего фрагмента кода? Ну, вы бы получили хэш строки Рабина-Карпа S, Но с тех пор F(x) является полиномом, мы можем использовать правило Хорнера для вычисления 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.

Другие вопросы по тегам