字符串匹配——RabinKarp算法

字符串匹配——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 q
03 ft <- 0; fp <- 0
04 for i <- 0 to m-1 // preprocessing
05  ft <- (d * ft + T[i]) mod q
06  fp <- (d * fp + P[i]) mod q
07 for s <- 0 to n - m // matching
08  if fp = ft then // run a loop to compare strings
09      if P[0...m-1] = T[s...s+m-1] return s
10  else ft <- ((ft - T[s]*c)*d + T[s+m]) mod q
11 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;
}


/** IN at the thought of though OUT 7 **/
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 of
though
模式串在主串的位置为:7
abcdefg
gfedcba
主串和模式串不匹配。
fgdajhkfhjaskdlfgbyue
yue
模式串在主串的位置为:18
frajlkfajsdlkfgjkljkleg
jsd
模式串在主串的位置为:8
aaaaaaaaaaaaaaa
a
模式串在主串的位置为: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)

你可能感兴趣的:(字符串,String,匹配,指纹,RabinKarp)