子字符串查找(4)——Rabin-Karp算法
2018-03-22 本文已影响0人
null12
一、定义
Rabin-Karp算法,是由M.O.Rabin和R.A.Karp发明的一种基于散列的字符串查找算法。
通常情况下,基于散列的字符串查找步骤是:
- 首先计算模式字符串的散列函数;
- 然后利用相同的散列函数计算文本中所有可能的M个字符的子字符串的散列函数值并寻找匹配
但是这种方法比暴力查找还慢,因为计算散列值会涉及字符串中的每个字符。Rabin和Karp对上述方法进行了改进,发明了一种能够在常数时间内算出M个字符的子字符串散列值的方法。
二、基本思想
以文本“3141592653589793”,模式串“26535”为例。
比较思路如下:
寻找一个大素数(作为散列表的大小),通过“除留余数法”计算模式串的散列值。然后依次计算文本中的相同长度子串的散列值,进行比较。
递推文本串的散列值:
以Ti表示文本字符T[i],Xi表示文本串T[i...M-1-i]的整数值,其中M为模式串长度,则:
递推可以得到:
我们可以在初始时求得字符串T[i...M-1-i]的hash值,即Xi%P = hash(txt, 0, M-1)(其中P为大素数);
然后通过上述公式递推就可以得到字符串T[i+1...M-i]的hash值,即Xi+1 % P。
三、实现
RK算法完整源码:
public int search(String txt, String ptn) {
int N = txt.length();
int M = ptn.length();
if (M > N)
return -1;
long txtHash = hash(txt, 0, M - 1); // 计算文本串txt[0...M-1]的hash值
long ptnHash = hash(ptn, 0, M - 1); // 计算模式串的hash值
// 首先做一次匹配
if (ptnHash == txtHash)
return 0;
// 计算R^(M-1)%P,后续公式递推求值用到
long RM = 1;
for (int i = 1; i <= M - 1; i++) {
RM = (RM * R) % P;
}
// 从文本的第1个字符开始查找
for (int i = 1; i <= N - M; i++) {
// 根据递推公式,计算文本串hash值
txtHash = (txtHash + P - RM * txt.charAt(i) % P) % P;
txtHash = (txtHash * R + txt.charAt(i + M)) % P;
if (txtHash == ptnHash)
return i;
}
return -1;
}
四、性能分析
Rabin-Karp算法,由于通过计算模式串和文本子串的散列值来做相等性比较,所以有一定概率出现冲突,即散列值相同但是字符串不匹配。
出现冲突的概率与大素数的选择有关,概率约为1/Q(Q为大素数的值),实际应用中,该算法是可靠的,只有极小的概率会出现冲突。
- 时间复杂度
O(N)