算法——字符串匹配之Rabin-Karp
来源:互联网 发布:pdf软件下载 编辑:程序博客网 时间:2024/05/29 10:58
有时间的读者请先看它 Rabin-Karp——geeksforgeeks
主字符串用S代替,长度为N;模式字符串用P代替,长度为M。
一种类似指纹的搜索想法:如果我们可以在O(M)的时间里计算P的指纹f(P),如果f(P) ≠ f(S[s…s+M-1])那么P ≠ S[s…s+M-1],如果我们可以在O(1)的时间里比较指纹,如果我们可以在O(1)的时间里从f(T[s…s+M-1])计算f(S[s+1…s+M]),那么字符串比较的速度将会加快。
举例:假设主字符串中只有数字0-9,我们搜索一个子串”1045”的位置。
1. 字母表: ∑ = {0,1,2,3,4,5,6,7,8,9},字母表大小为10;
2. 指纹计算:f(“1045”) = 1*10^3+0*10^2+4*10^1+5*10^0 = 1045
3. 算法伪代码描述:
Fingerprint_Search(S, P)01: fp <- compute f(P) // 预处理02: f <- compute f(S[0...M-1]) // 预处理03: for i <- 0 to N-M Do // 开始搜索04: if fp = f return i05: f = (f-S[i]*10^(m-1))*10+S[i+M]06: return -1
以上的伪代码对搜索0-9数字看起来成立,而且运行时间为2O(M)+O(N-M)=O(N+M)很好。可是我们如何搜索其他字符呢,我们如何能在非常快的时间里计算出指纹呢?不过以上的如果和问题都可以得到解决,接下来介绍Rabin-Karp算法。
Rabin-Karp
Rabin-Karp字符串匹配算法和之前介绍的朴素匹配算法类似,也是对每一个字符进行比较。不同的是Rabin-Karp采用了把字符进行预处理,通过某种函数计算其函数值(指纹),比较的是每个字符的函数值。
算法分析:预处理时间O(M),匹配时间是O(N-M)(最好)O((N-M+1)M)(最坏)。
计算指纹的函数如何选取?采用Hash函数,简单来说就是取模运算:h = f mod q(q为素数,将函数值限制在q之内)
(1) 如果q=7,那么h(“52”) = f mod q = 52 mod 7 = 3;
(2) 如果h(s1) ≠ h(s2),那么s1 ≠ s2;
(3) 如果h(s1) = h(s2),不代表s1 = s2;(q = 7 时,h(“52”)=h(“94”),但”52”≠”94”,这种情况下我们需要按位比较每一个字符)
(4) (a+b) mod q = (a mod q + b mod q) mod q;
(5) (a*b) mod q = (a mod q * b mod q )mod qRabin-Karp字符串匹配如何扩展到字母甚至所有字符?比较数字时,我们定义字母表为{0,1,2,3,4,5,6,7,8,9},字母表大小为10,使用十进制。若比较字母的话,我们就可以定义字母表为{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z},使用26进制。那么我们计算hash函数值的时候就可以变化为f(“abcd”) = 0*26^3 + 1*26^2 + 2 * 26^1 + 3 * 26^0 = 731(该例子a,b,c,d看作1,2,3,4,方便理解。实际代码中我们用的是ascii码,因为char字符在进行加减乘除运算中会自动转化为int值)。若是比较数字加字母,定义字母表为二者的并集,进制变为36。
算法中的几个细节(仍然按照十进制来讲解)
预处理中
(1) fp = (p[m-1] + 10*(p[m-2]+ 10*(p[m-3]+…+10*(p[1]+10*p[0]))))modq(秦九韶算法,将一元n次多项式的求值问题转化为n个一次式的算法,简化计算过程,在西方被称作霍纳[Horner]算法);
(2) f(S[0…m-1]) 计算方法 同(1);
循环比较中
(3) f(S[i+1…i+M]) = (f(S[i…i+M-1])-S[i]*10M−1 )*10 + S[i + M]) mod q;
(4) 其中10M−1 可以在预处理中计算出来结果。算法优化后的伪代码描述
Rabin_Karp_Search(S, P)01: q <- a prime number larger than M(Plength)02: c <- 10^(M-1)mod q03: fp <- 0; fs <- 0;04: for i <- 0 to M-105: fp <- (10 * fp + P[i]) mod q06: fs <- (10 * fs + S[i]) mod q07: for i <- 0 to N-M08: if fp = ft then // run a loop to compare string(hash函数值相等则匹配字符串)09: if P[0...M-1] = T[i...i+M-1] return i10: fs = ((fs - S[i]*c)+S[i+M]) mod q11: return -1
Rabin-Karp再分析
(1) q 为素数, hash函数会使M位字符串的hash函数值在q个值内均匀分布(q越大越好)。所以,比较字符串外层循环从i = 0 —> N-M中,每q次才需要出现hash函数值相等的情况,此情况下需要比较字符串。因为出现的概率低,所以算法会比较快。
(2) 选择位数 > M 的素数 q,可以利用随机算法在O(M)完成。
(3) 预处理时间O(M),外层循环为O(N-M),所以所有内循环之和为(N-M)* M/q = O(N-M),所以运行时间为O(N-M)。
(4) 最坏运行时间O(NM),例如S=”aaaaaaaaaaaaab” P=”aaab”Rabin-Karp代码(Java版)
public class RabinKarpSearch { // d is the number of characters in input alphabet public final static int d = 256; public static void search( String S, String P, int q) { int N = S.length(); int M = P.length(); int t = 0; // hash value for S:txt int p = 0; // hash value for P:pattern int h = 1; int i, j; // The value of h would be "pow(d, M-1)%q" for (i = 0; i < M - 1; i++) h = (h * d) % q; // Calculate the hash value of pattern and S[0...M-1] for (i = 0; i < M; i++) { p = (d * p + P.charAt(i)) % q; t = (d * t + S.charAt(i)) % q; } // Slide the pattern over text one by one for (i = 0; i <= N - M; i++) { // Check the hash values of current window of text and pattern. // If the hash values match then check for characters on by one if (p == t) { for (j = 0; j < M; j++) { if (S.charAt(i + j) != P.charAt(j)) break; } // if p == t and pat[0...M-1] = txt[i, i+1, ...i+M-1] if (j == M) System.out.println("Pattern found at index " + i); } // Calculate hash value for next window of text: Remove // leading digit, add trailing digit if (i < N - M) { t = ( (t - S.charAt(i) * h) * d + S.charAt(i + M)) % q; // We might get negative value of t, converting it to positive if (t < 0) t = (t + q); } } } public static void main(String[] args) { String S = "GEEKS FOR GEEKS"; String P = "GEEK"; int q = 101; // A prime number search(S, P, q); }}
参考资料:
[1]. Rabin-Karp——geeksforgeeks
[2]. 算法——字符串匹配之Rabin-Karp算法
[3]. 面试算法之字符串匹配算法,Rabin-Karp算法详解
- 算法——字符串匹配之Rabin-Karp算法
- 算法——字符串匹配之Rabin-Karp
- 字符串匹配算法之Rabin-Karp算法
- 字符串匹配之Rabin-Karp算法
- 字符串匹配之Rabin-Karp 算法
- 白话分析字符串匹配算法——Rabin-Karp算法
- Rabin-Karp字符串匹配算法
- 字符串匹配--Karp-Rabin算法
- Rabin-Karp字符串匹配算法
- Rabin-Karp 字符串匹配算法
- 面试算法之字符串匹配算法,Rabin-Karp算法详解
- 字符串匹配算法 -- Rabin-Karp 算法
- 字符串匹配算法 朴素算法 Rabin—Karp算法,KMP算法
- Rabin-Karp字符串匹配算法c源代码
- 字符串匹配 之 RK(Rabin-Karp)
- 字符串匹配(一)——朴素算法,Rabin-Karp算法
- 字符串算法-Rabin-Karp
- 4042:Rabin-Karp字符串匹配
- Java 网络编程一
- JAVA常用快捷键
- CSS基础(二)
- 2017百度之星初赛(B)1006小小粉丝度度熊------hdu6119
- Android开发常用的工具类集合
- 算法——字符串匹配之Rabin-Karp
- debian之网易云音乐的安装
- 设计模式
- 用户在线、离线、忙碌功能设计与实现
- ZOJ 1489 HDU1395 2^x mod n = 1 数学
- (并查集的应用)LeetCode#547. Friend Circles
- 进程和线程的关系以及区别
- [SMOJ2166]数列的和
- env, set, export 命令的区别