字符串匹配——RabinKarp算法

来源:互联网 发布:视频软件大全 编辑:程序博客网 时间:2024/06/10 16:43

字符串匹配——RabinKarp算法

给定主串T和模式串P,返回P在T中首次出现的位置,如果P不存在于T中,返回-1。

这样的问题就是字符串匹配问题,这里给出RabinKarp算法的思想。

设主串T的长度为n,模式串P的长度为m。

主串匹配起始位置s从0到n-m,计算出T[s..s+m-1]的对应值,与P[0…m-1]的对应值进行比较,如果相同,则匹配成功。不同,则s右移一位,也就是计算出T[s+1…s+m]的对应值。

对应值的计算方法可以称作简单的指纹算法。

RabinKarp算法中的指纹算法是基于hash函数的。

其原理为 h = f % q,假设q=5,hash(12)= 12 % 5 = 2

hash函数的性质:

  • if hash(s1) ≠ hash(s2) then s1 ≠ s2
  • 但是hash(s1) = hash(s2)不意味着s1 = s2

模运算的性质:

  • (a + b) % q = (a % q + b % q) % q
  • (a * b) % q = ((a % q) * (b % q)) % q

假设字母表大小为d,hash因子为q,主串为T,长度为n,当前起始位置为s,模式串为P,长度为m,fp为P的指纹值,ft为T当前的指纹值。

预处理:

  • fp = (P[m - 1] + d * (P[m - 2] + d * (P[m - 3] + … + d * ((P[1] + d * (P[0] % q)) % q)…) % q
  • ft = (T[m - 1] + d * (T[m - 2] + d * (T[m - 3] + … + d * ((T[1] + d * (T[0] % q)) % q)…) % q

ft的迭代过程(右移一位):

  • ft = (ft - T[s] * 10m1) * 10 + T[s + m]) % q

伪代码

假设字母表大小为d

RabinKarp(T, P)01 q <- a prime larger than m or less than the  machine word length / d 02 c <- d^(m-1) mod q // run a loop multiplying by d mod q03 ft <- 0; fp <- 004 for i <- 0 to m-1 // preprocessing05  ft <- (d * ft + T[i]) mod q06  fp <- (d * fp + P[i]) mod q07 for s <- 0 to n - m // matching08  if fp = ft then // run a loop to compare strings09      if P[0...m-1] = T[s...s+m-1] return s10  else ft <- ((ft - T[s]*c)*d + T[s+m]) mod q11 return -1

实现代码

生成字母表

// 字母表int alphaBet[maxNum];// 字母表字母个数,也就是d进制int d;// 生成字母表,返回字母表字母个数int markAlphaBet(string T, string P) {    // 初始化字母表    memset(alphaBet, 0, sizeof(alphaBet));    int d = 0;    for(int i = 0; i < T.length(); i++) {        if(!alphaBet[T[i]]) {            alphaBet[T[i]] = d++;        }    }    for(int i = 0; i < P.length(); i++) {        if(!alphaBet[P[i]]) {            alphaBet[P[i]] = d++;        }    }    return d;}

RabinKarp算法

const int prime = 9999991;int rabinKarp(string T, string P) {    // 字符串长度    int n = T.length();    int m = P.length();    // 计算d^(m-1) mod q    int c = 1;    for(int i = 0; i < m - 1; i++) {        c = (d * c) % prime;    }    // 初始化    int ft = 0;    int fp = 0;    // 预处理,计算T[0...m-1]和P[0...m-1]的hash值    for(int i = 0; i < m; i++) {        ft = (d * ft + alphaBet[T[i]]) % prime;        fp = (d * fp + alphaBet[P[i]]) % prime;    }    // 匹配    for(int i = 0; i <= n - m; i++) {        // hash值相同        if(ft == fp) {            // 判断字符串T[i...i+m-1]与P[0...m-1]是否相等            for(int j = 0; j < m; j++) {                if(T[i + j] != P[j]) {                    break;                }                if(j == m - 1) {                    return i;                }            }        } else {            // 计算T[i+1...i+m]的hash值            ft = ((ft - alphaBet[T[i]] * c) * d + alphaBet[T[i + m]]) % prime;        }    }    return -1;}

测试主程序

#include <iostream>#include <cstring>using namespace std;const int maxNum = 1000 + 5;// 字母表int alphaBet[maxNum];// 字母表字母个数,也就是d进制int d;// 生成字母表,返回字母表字母个数int markAlphaBet(string T, string P) {    // 初始化字母表    memset(alphaBet, 0, sizeof(alphaBet));    int d = 0;    for(int i = 0; i < T.length(); i++) {        if(!alphaBet[T[i]]) {            alphaBet[T[i]] = d++;        }    }    for(int i = 0; i < P.length(); i++) {        if(!alphaBet[P[i]]) {            alphaBet[P[i]] = d++;        }    }    return d;}const int prime = 9999991;int rabinKarp(string T, string P) {    // 字符串长度    int n = T.length();    int m = P.length();    // 计算d^(m-1) mod q    int c = 1;    for(int i = 0; i < m - 1; i++) {        c = (d * c) % prime;    }    // 初始化    int ft = 0;    int fp = 0;    // 预处理,计算T[0...m-1]和P[0...m-1]的hash值    for(int i = 0; i < m; i++) {        ft = (d * ft + alphaBet[T[i]]) % prime;        fp = (d * fp + alphaBet[P[i]]) % prime;    }    // 匹配    for(int i = 0; i <= n - m; i++) {        // hash值相同        if(ft == fp) {            // 判断字符串T[i...i+m-1]与P[0...m-1]是否相等            for(int j = 0; j < m; j++) {                if(T[i + j] != P[j]) {                    break;                }                if(j == m - 1) {                    return i;                }            }        } else {            // 计算T[i+1...i+m]的hash值            ft = ((ft - alphaBet[T[i]] * c) * d + alphaBet[T[i + m]]) % prime;        }    }    return -1;}/**INat the thought ofthoughOUT7**/int main() {    // 主串和模式串    string T, P;    while(true) {        // 获取一行        getline(cin, T);        getline(cin, P);        d = markAlphaBet(T, P);        int res = rabinKarp(T, P);        if(res == -1) {            cout << "主串和模式串不匹配。" << endl;        } else {            cout << "模式串在主串的位置为:" << res << endl;        }    }    return 0;}

输出数据

at the thought ofthough模式串在主串的位置为:7abcdefggfedcba主串和模式串不匹配。fgdajhkfhjaskdlfgbyueyue模式串在主串的位置为:18frajlkfajsdlkfgjkljklegjsd模式串在主串的位置为:8aaaaaaaaaaaaaaaa模式串在主串的位置为:0

算法分析

  • if q 是素数, hash函数将会使m位字符串在q个值中均匀分布
    • 因此,仅有s个轮换中的每第q次才需要匹配指纹(匹配需要比较O(m) 次)
  • 期望运行时间(如果q > m):
    • 生成字母表:O(n + m)
    • 预处理:O(m)
    • 外循环:O(n - m)
    • 所有内循环:nmqm=O(nm)
    • 总时间:O(n + m)
  • 最坏运行时间:O(nm)
0 0
原创粉丝点击