Rabin-Karp ( 字符串匹配 )详解

26 阅读2分钟

字符串匹配,例从 字符串S 中( 长度为 n ),找到 字符串T ( 长度为 m )

经典思路:遍历 字符串 S,对于每个都为起点,匹配一次,则 O( n m )的复杂度
但是这样我们就对 字符 重复判断了多次
请添加图片描述

那我们应该怎么处理才能避免多次对同一个字符判断呢?
我们先考虑假如 字符串S 和 字符串T 是由数字组成
例如 S 为 “19735859734”,T 为 “973”
第一次比较 197 和 973
第二次比较 (197 - 1 * 100)* 10 + 3 = 973 和 973
第三次比较 (973 - 9 * 100)* 10 + 5 = 735 和 973
第四次…

这样我们就可以从头到尾以 O( n )的复杂度处理

这样为什么可行呢,因为这个是 十进制 的,每位上的数,都可以代表清晰表示第几个字符是什么
那我们推广这种思路,我们是否只要把 十进制 改为 B 进制( B 大于等于 字符的种类),这样每位上的数也能表达第几个字符是什么

如果字符种类比较多可以把 B 取大一点,但这样我们可能需要对 h 取模,毕竟 B 的 m 次方,防止溢出,因为我们只要比较不同就行,不用比较大小,所以可以取模( 注意 B 与 h 要互素 )

注意一下:有可能不同字符的哈希值相同,如果发生这种情况,我们还需要进行朴素的检查( 即一个个字符比较 ),这种可能会导致复杂度退化为 O( n m ),但我们在竞赛题中,只用比较哈希值就行

实现代码如下( 比较 字符串b 中有几个 字符串a ):

const int B = 128;
int compare(string a, string b)
{
    int res = 0;
    int al = a.length(), bl = b.length(), t = 1;
    if(al > bl) return 0;
    for(int i = 0; i < al; i++)
    {
        t = t * B;
    }
    int ah = 0, bh = 0;
    for(int i = 0; i < al; i++)
    {
        ah = ah * B + a[i];
        bh = bh * B + b[i];
    }
    for(int i = 0; i + al <= bl; i++)
    {
        if(ah == bh) res++;
        if(i + al < bl) bh = bh * B + b[i+al] - b[i] * t;
    }
    return res;
}