如何将 mod 合并到 Rabin Karp 算法的滚动哈希中?
How to incorporate mod in rolling hash of Rabin Karp algorithm?
我正在尝试使用 mod 实现 Rabin Karp 算法。我使用的哈希函数是:
H1= c1*a^k-1 + c2*a^k-2 +c3*a^k-3 +…+ck*a^0
这里的cx是字符的ASCII值。为了滚动它,我首先通过减去第一项来删除它,然后乘以 a 并通过将它与 a^0 相乘来添加新项。
现在的问题是处理大值我已经使用 mod 操作但是这样做我无法正确滚动它。我的代码如下:
public class RabinKarp {
private static final int base = 26;
private static final int mod = 1180637;
public static void main(String[] args) {
String text = "ATCAAGTTACCAATA";
String pattern = "ATA";
char[] textArr = text.toCharArray();
char[] patternArr = pattern.toCharArray();
System.out.println(getMatchingIndex(textArr, patternArr));
}
public static int getMatchingIndex(char[] textArr, char[] patternArr) {
int n = textArr.length;
int m = patternArr.length;
int patternHash = getHashForPatternSize(patternArr, m);
int textHash = getHashForPatternSize(textArr, m);
for(int i = 0; i < n-m; i++) {
if(patternHash == textHash && checkMatch(textArr, patternArr, i, m))
return i;
textHash = rollingHash(textArr, textHash, i, m);
}
return -1;
}
public static boolean checkMatch(char[] textArr, char[] patternArr, int i, int m) {
for(int j = 0; j < m; j++,i++) {
if(textArr[i] != patternArr[j])
return false;
}
return true;
}
public static int rollingHash(char[] textArr, int textHash, int i, int m) {
return (textHash * base - modularExponentiation(base, m, mod) * (int)textArr[i] + (int) textArr[i+m])%mod;
}
public static int getHashForPatternSize(char[] arr, int m) {
int hash = 0;
for(int i = 0, p = m; i < m; i++, p--) {
hash = (hash%mod + calcHash(arr[i], p)%mod)%mod;
}
return hash;
}
public static int calcHash(char alphabet, int p) {
return (((int) alphabet)%mod * modularExponentiation(base, p, mod)%mod)%mod;
}
public static int modularExponentiation(int base, int p, int mod) {
if(p == 0)
return 1;
if(p%2 == 0)
return modularExponentiation((base*base)%mod, p/2, mod);
else
return (base*modularExponentiation((base*base)%mod, (p-1)/2, mod))%mod;
}
}
问题是 textHash
和 patternHash
在任何时候都不匹配。我确定问题出在 mod 操作上。任何人都可以告诉如何拥有 mod 以及如何正确使用滚动哈希。我将不胜感激。
计算 Rabin-Karp 滚动哈希的常用方法是按大端顺序考虑字符,而不是小端解决方案。这使得算术更容易,因为它避免了除法。模块化除法非常重要,您不能简单地将其实现为 (p/q)%b
.
如果我们将滚动散列作为
H<sub>0…k-1</sub> = (c<sub>0</sub>*a<sup>k-1</sup> + c<sub>1</sub>*a<sup>k-2</sup> + c<sub>2</sub>*a<sup>k-3</sup> …+… c<sub>k-1</sub>*a<sup>0</sup>) mod b
那么下学期是:
H<sub>1…k</sub> = ( c<sub>1</sub>*a<sup>k-1</sup> + c<sub>2</sub>*a<sup>k-2</sup> …+… c<sub>k-1</sub>*a<sup>1</sup> + c<sub>k</sub>*a<sup>0</sup>) mod b
而且我们很容易看出
H<sub>1…k</sub> = (a * H<sub>0…k-1</sub> - c<sub>0</sub>*a<sup>k</sup> + c<sub>k</sub>) mod b
如果我们再预先计算 m == a<sup>k</sup> mod b
,那就变成:
H<sub>1…k</sub> = (a * H<sub>0…k-1</sub> - m * c<sub>0</sub> + c<sub>k</sub>) mod b
每次迭代的工作量要少得多,而且根本不依赖于除法。
我正在尝试使用 mod 实现 Rabin Karp 算法。我使用的哈希函数是:
H1= c1*a^k-1 + c2*a^k-2 +c3*a^k-3 +…+ck*a^0
这里的cx是字符的ASCII值。为了滚动它,我首先通过减去第一项来删除它,然后乘以 a 并通过将它与 a^0 相乘来添加新项。
现在的问题是处理大值我已经使用 mod 操作但是这样做我无法正确滚动它。我的代码如下:
public class RabinKarp {
private static final int base = 26;
private static final int mod = 1180637;
public static void main(String[] args) {
String text = "ATCAAGTTACCAATA";
String pattern = "ATA";
char[] textArr = text.toCharArray();
char[] patternArr = pattern.toCharArray();
System.out.println(getMatchingIndex(textArr, patternArr));
}
public static int getMatchingIndex(char[] textArr, char[] patternArr) {
int n = textArr.length;
int m = patternArr.length;
int patternHash = getHashForPatternSize(patternArr, m);
int textHash = getHashForPatternSize(textArr, m);
for(int i = 0; i < n-m; i++) {
if(patternHash == textHash && checkMatch(textArr, patternArr, i, m))
return i;
textHash = rollingHash(textArr, textHash, i, m);
}
return -1;
}
public static boolean checkMatch(char[] textArr, char[] patternArr, int i, int m) {
for(int j = 0; j < m; j++,i++) {
if(textArr[i] != patternArr[j])
return false;
}
return true;
}
public static int rollingHash(char[] textArr, int textHash, int i, int m) {
return (textHash * base - modularExponentiation(base, m, mod) * (int)textArr[i] + (int) textArr[i+m])%mod;
}
public static int getHashForPatternSize(char[] arr, int m) {
int hash = 0;
for(int i = 0, p = m; i < m; i++, p--) {
hash = (hash%mod + calcHash(arr[i], p)%mod)%mod;
}
return hash;
}
public static int calcHash(char alphabet, int p) {
return (((int) alphabet)%mod * modularExponentiation(base, p, mod)%mod)%mod;
}
public static int modularExponentiation(int base, int p, int mod) {
if(p == 0)
return 1;
if(p%2 == 0)
return modularExponentiation((base*base)%mod, p/2, mod);
else
return (base*modularExponentiation((base*base)%mod, (p-1)/2, mod))%mod;
}
}
问题是 textHash
和 patternHash
在任何时候都不匹配。我确定问题出在 mod 操作上。任何人都可以告诉如何拥有 mod 以及如何正确使用滚动哈希。我将不胜感激。
计算 Rabin-Karp 滚动哈希的常用方法是按大端顺序考虑字符,而不是小端解决方案。这使得算术更容易,因为它避免了除法。模块化除法非常重要,您不能简单地将其实现为 (p/q)%b
.
如果我们将滚动散列作为
H<sub>0…k-1</sub> = (c<sub>0</sub>*a<sup>k-1</sup> + c<sub>1</sub>*a<sup>k-2</sup> + c<sub>2</sub>*a<sup>k-3</sup> …+… c<sub>k-1</sub>*a<sup>0</sup>) mod b
那么下学期是:
H<sub>1…k</sub> = ( c<sub>1</sub>*a<sup>k-1</sup> + c<sub>2</sub>*a<sup>k-2</sup> …+… c<sub>k-1</sub>*a<sup>1</sup> + c<sub>k</sub>*a<sup>0</sup>) mod b
而且我们很容易看出
H<sub>1…k</sub> = (a * H<sub>0…k-1</sub> - c<sub>0</sub>*a<sup>k</sup> + c<sub>k</sub>) mod b
如果我们再预先计算 m == a<sup>k</sup> mod b
,那就变成:
H<sub>1…k</sub> = (a * H<sub>0…k-1</sub> - m * c<sub>0</sub> + c<sub>k</sub>) mod b
每次迭代的工作量要少得多,而且根本不依赖于除法。