Eu tenho lido sobre o algoritmo Rabin Karp e fiquei pensando qual é o grande problema em manter nossos valores de hashes rotativos limitados por um valor Q?
Eu pensava que, como nossa representação de número inteiro no computador típico é de 2 complementos, na verdade é exatamente equivalente a limitar todas as nossas operações sobre os hashes rotativos por 2 ^ 31, de modo que, em outras palavras, eu simplesmente não deveria me importar. Além disso, quanto menor o limite ou o hash, mais colisões teríamos, portanto, um Q maior seria igual ao desempenho aprimorado!
Eu tentei codificar uma implementação simples (Java):
public static int rabinKarp(String text, String pattern) {
if (text.length() < pattern.length()) {
return -1;
} else {
int patternHash = 0;
int textHash = 0;
int pow = 1;
// preprocessing the pattern and the first characters of the text string
for (int i = pattern.length()-1; i >= 0; --i) {
patternHash += pattern.charAt(i) * pow;
textHash += text.charAt(i) * pow;
pow *= 10;
}
pow /= 10;
// actual search
if (patternHash == textHash && areEqual(text, 0, pattern)) {
return 0;
} else {
for (int i = 1; i < text.length()-pattern.length()+1; ++i) {
textHash -= text.charAt(i-1)*pow;
textHash *= 10;
textHash += text.charAt(i+pattern.length()-1);
if (textHash == patternHash && areEqual(text, i, pattern)) {
return i;
}
}
return -1;
}
}
}
A partir de alguns testes preliminares, minha hipótese parece ser empiricamente precisa, mas ainda não a vi escrita em lugar algum, por isso fico pensando ..
Estou esquecendo de algo?
fonte
Respostas:
Sim, na prática, você pode se dar bem apenas deixando os cálculos transbordarem. Você está efetivamente trabalhando módulo232. . Ele também tem a vantagem de não exigir um cálculo de módulo (caro). No entanto, falta algumas das garantias teóricas de desempenho. Você precisa ter muito cuidado com a escolha da base (neste caso:10 ) em relação ao módulo.
Em particular, sua escolha de10 é muito pobre. Observe que1032.=232.⋅532. , tão 1032. mod 232.= 0 . Isso significa que apenas o último32. caracteres da string são levados em consideração no hash, para que se possa construir uma entrada na qual seu algoritmo tenha um desempenho muito ruim.
Deixe o palheiro ser uma sequência dem 1 1 é 1111111 … e a agulha uma corda consistindo de n 1 1 é um 0 0 , e depois 32. 1 1 's. Como a sequência termina com32. 1 1 todas as posições resultarão em um golpe falso, e o algoritmo precisará passar por cima n 1 1 antes de encontrar um zero, o que significa que você receberá um Ω ( n m ) tempo de execução.
Testei seu algoritmo em uma entrada em quen = 3000 , m =n2= 9 ⋅106 . Levou18 segundos para executar em uma entrada que terminou em 32. 1, mas apenas 200 ms para uma sequência terminada em 31 1 1 's.
O problema é que10 não é relativamente primordial para o módulo. Por exemplo, tomar9 como a base melhora o desempenho do seu programa, levando apenas 200 m s para o caso com 32. 1 1 's. Obviamente, tomar um módulo primo resolverá parcialmente esse problema, já que a base será automaticamente relativamente privilegiada. No entanto, este não é o único motivo para preferir um módulo principal.
Agora, mesmo que o módulon e base b são relativamente excelentes, coisas indesejáveis ainda podem acontecer. Por exemplo, há umk para qual bk= 1 mod n . É indesejável parak ser pequeno, pois a função hash não pode distinguir todos os Euº personagem de todos i +kº personagem. Em termos matemáticos, você deseja a ordem deb mod n ser o maior possível.
A ordem deb mod n é sempre no máximo a função Euler-Phi φ ( n ) . Para um primop , ϕ ( p ) = p - 1 enquanto para não primos n será menor. Então, tomandon ser primo permitirá mais dos valores de bk Ser útil". Idealmente, deve-se tomarb ser um módulo raiz primitivo n fazendo isso bk= 1 mod n não vale para nenhum valor de 0 < k < ϕ ( n ) .
Observe que você sempre pode construir instâncias para as quais o desempenho é ruim e, para se proteger contra "ataques" de um adversário, é necessário que a base e o módulo sejam valores aleatórios.
fonte