字符串匹配——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] *
10m−1 ) * 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)
- 所有内循环:
n−mqm=O(n−m) - 总时间:O(n + m)
- 最坏运行时间:O(nm)
0 0
- 字符串匹配——RabinKarp算法
- 字符串匹配的RabinKarp算法的c语言实现
- 字符串匹配 — BM算法
- 算法导论—字符串匹配
- 字符串匹配—KMP算法
- 字符串算法——字符串匹配
- 字符串匹配算法——BM算法
- 字符串匹配算法——KMP算法
- 字符串匹配算法——KMP算法
- 字符串匹配算法——KMP算法
- KMP 算法 —— 字符串匹配算法
- 字符串匹配算法——KMP算法
- KMP 算法总结—字符串匹配算法
- 算法——字符串匹配之朴素匹配算法
- 字符串模式匹配——KMP算法
- 朴素字符串匹配——算法导论
- 字符串模式匹配——KMP算法
- KMP算法——字符串匹配问题
- SQLite小白成长记,创建数据库,创建表,版本的重要性,没有创建项
- WebService学习——利用Eclipse生成JAX-WS WebService客户端
- fresco开篇
- 过滤网址和输入框中的特殊字符,防止sql注入(C#版)
- 浅谈算法和数据结构: 七 二叉查找树
- 字符串匹配——RabinKarp算法
- 剑指offer(26)-最小的K个数
- Boa服务器移植及应用(一)
- s.decode('unicode-escape')
- 浅谈算法和数据结构: 八 平衡查找树之2-3树
- mvp模式
- ThinkPHP教程
- 如何快速的定位程序中某个功能对应的代码?
- Android Studio构建优化