子字符串查找(4)——Rabin-Karp算法

一、定义

Rabin-Karp算法,是由M.O.Rabin和R.A.Karp发明的一种基于散列的字符串查找算法。
通常情况下,基于散列的字符串查找步骤是:

  1. 首先计算模式字符串的散列函数;
  2. 然后利用相同的散列函数计算文本中所有可能的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)

你可能感兴趣的:(子字符串查找(4)——Rabin-Karp算法)