字符串匹配之RKHash算法

对于任意一个串,串中字符集的大小为d,则该串中的任意一个字符都可以用一个d+1进制的整数来表示。需要注意的是,这里是d+1进制,而不是d进制,是因为不能用0来表示任意一个字符,否则如果该字符组成串的一个前缀,无论前缀的长度多少,都不会影响串所对应的整数取值。

Rabin-Karp算法,它是字符串快速查找的一种算法,解决思路是把一个字符串,看作是字符集长度进制的树,如果是ASCII,这个进制就是128,如果是只考虑英文小写字母,那这个进制就是26,通过数值的比较得出字符串的比较结果。

如果我们要在 ASCII 字符集范围内查找“搜索词”,由于 ASCII 字符集是0~127一共 128 个字符,那么 d 就等于 128,比如我们要在字符串 “abcdefg” 中查找 “cde”,那么我们授信计算模式字符串 “cde”的hash指纹:

n=len(“cde”)

d=128

Hash(cde)= c_ASCII*d^(n-1)+ d_ASCII*d^(n-2)+ e_ASCII*d^(n-3)

          =99*128^2+100*128^1+101*128^0

=1634917

模式字符串的hash值计算完之后开始计算源串的hash值,计算方法与上面相同:

Hash(abc)= a_ASCII*d^(n-1)+ b_ASCII*d^(n-2)+ c_ASCII*d^(n-3)

          =97*128^2+98*128^1+99*128^0

=1601891

其中:

Hash(a)= a_ASCII*d^(n-1) =97*128^2= 1589248

Hash(b)= b_ASCII*d^(n-1) =98*128^1= 12544

Hash(c)= c_ASCII*d^(n-1) =99*128^0=99

显而易见的Hash(abc)不等于Hash(cde),源串的窗口向后滑动一位计算hash(bcd), hash(bcd)的计算如果还是按照上面的方法的话Rabin-Karp的时间复杂度和暴力解法一样也是O(m*n),hash(bcd) 可以看成hash(abc)中去掉Hash(a),然后加入Hash(d),如此一来就将计算指纹的时间复杂度由O(n)降低到O(1):

Hash(bcd)= (Hash(abc)- Hash(a))*d+ Hash(d),这样就可以继续比较 “cde” 和 “bcd” 是否匹配,以此类推。

由上面的描述可以推导出更一般的规律如下:

hash( txt[s+1 .. s+m] ) = ( d ( hash( txt[s .. s+m-1]) – txt[s]*h ) + txt[s + m] ) mod q

hash( txt[s .. s+m-1] ) : Hash value at shift s .
hash( txt[s+1 .. s+m] ) : Hash value at next shift (or shift s +1)
d : Number of characters in the alphabet
q : A prime number
h: d^(m-1)

加入模q后, H = H’mod q.对于指纹相同的串还要在进行一次比对校验,如果q足够大,碰撞的几率会很少出现。

Rabin-Karp算法的思想:

    假设子串的长度为M,目标字符串的长度为N

    计算子串的hash值

    计算目标字符串中每个长度为M的子串的hash值(共需要计算N-M+1次)

    比较hash值

    如果hash值不同,字符串必然不匹配,如果hash值相同,还需要使用朴素算法再次判断

为了快速的计算出目标字符串中每一个子串的hash值,Rabin-Karp算法并不是对目标字符串的 每一个长度为M的子串都重新计算hash值,而是在前几个字串的基础之上, 计算下一个子串的 hash值,这就加快了hash之的计算速度,将算法中的内循环的世间复杂度从O(M)将到了O(1)。

代码示例:

d取字符集的个数

#define d 256


/*  S  -> Search Sequence
    T  -> Target Sequence
    q  -> A prime number
*/

void search(char *S, char *T, int q) {
    int M = strlen(S);
    int N = strlen(T);
    int i, j;
    int SHashValue = 0;  // hash value for Search Sequence
    int THashValue = 0;  // hash value for Target Sequence
    int h = 1;           // h 的计算公式:pow(d, M-1) % q

    //计算h
    for (i = 0; i < M-1; i++)
        h = (h * d) % q;

    for (i = 0; i < M; i++) {
        SHashValue = (d * SHashValue + S[i]) % q;
        THashValue = (d * THashValue + T[i]) % q;
    }

    for (i = 0; i <= N - M; i++) {   

        if ( SHashValue == THashValue ) {
            for (j = 0; j < M; j++) {
                if (txt[i+j] != pat[j])
                    break;
            }

            if (j == M) {    // if SHashValue == THashValue and S[0...M-1] = T[i, i+1, ...i+M-1]
                cout << "Pattern found at index " << i << endl;
            }
        }

        if ( i < N-M )
        {
            THashValue = (d * (THashValue - T[i] * h) + T[i+M]) % q;
            if(THashValue < 0)
              THashValue = (THashValue + q);
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值