MP 算法是一种快速串匹配算法,对 BF 算法的改进很大,主要体如今匹配失败时,目标指针不用回溯,而是利用已经获得的“部分匹配”结果,将模式向右“滑动”若干位置后继续比较,避免了频繁回溯,广泛提升了匹配的工做效率,所以又被称为不回溯的字符串搜索算法。算法
假设有目标串T(t₀,t₁,t₂,t₃,……,tn-1)和模式串P(p₀,p₁,p₂,p₃,……,pm-1),若使用BF算法进行模式匹配,第一轮比较时,若tk≠pk,则算法结束这轮比较。函数
字符串T和P中第一个不相等的字符位置出如今位置k处,因此两串前k个字符是相等的,能够用字符串P(p₀,p₁,p₂,p₃,……,pk-1)代替字符串T'(t₀,t₁,t₂,t₃,……,tk-1),因而原目标串可转化为T(p₀,p₁,p₂,p₃,……,pk-1,tk,...,tn-1)。在进行第二次比较以前,算法一样把字符串P总体向后移动一个字符,此时,T与P的关系:spa
在上面的比较中,首先比较的是 P中的首字符p0 与 T中的第2个字符p1,若与相等,则算法顺序比较 P中第2个字符P1 与 T中第3个字符P2,若不相等,则算法将模式串P总体向后移动一个字符,此时T与P之间的关系:指针
算法依照相同的次序,首先对 P中字符p0 与 T中字符p2 进行比较,若相等则顺序比较后续字符,若不相等,则把P总体向后移动一个字符。code
从上面的流程描述,都是对模式串的字符做比较,因此MP算法先是计算出模式字符串(串P)中各个字符之间的关系,而后再依据此关系与目标字符串(串T)进行匹配。记录串P中各个字符之间的关系的函数也被称为字符串P的失效函数。blog
失效函数的定义域为 j∈{0, 1, 2, 3, 4, 5, 6},也就是 0~Len(P)-1,Len(P)为串P的长度。字符串
失效函数的值域的计算:对于 k∈{x | 0≤x<j},且 k 知足 p0 p1 … pk = pj-k pj-k+1 … pj 的最大正整数。table
对于模式串P“caatcat”的失效函数实例(不能知足条件的k不存在则为-1):class
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
p(j) | c | a | a | t | c | a | t |
f(j) | -1 | -1 | -1 | -1 | 0 | 1 | -1 |
:效率
① 当 j = 0,因为 0≤k<0,因此知足条件的 k 并不存在,因此 j 取 0,f(0) = -1。
② 当 j = 4,k 的可能取值有 0,1,2,3,因为 p0 = p4,p0p1 ≠ p3p4,p0p1p2 ≠ p2p3p4 以及 p0p1p2p3 ≠ p1p2p3p4,因此 f(4) = 0。
③ 当 j = 5,k 的可能取值有 0,1,2,3,4,同理 p0 ≠ p5,p0p1 = p4p5,p0p1p2 ≠ p3p4p5 ,p0p1p2p3 ≠ p2p3p4p5 以及 p0p1p2p3p4 ≠ p1p2p3p4p5,因此 f(5) = 1。
获得字符串P的失效函数后,就能够应用 MP 算法对它进行匹配。
总结一下,上面所述的是在求字符串前缀后缀的部分匹配值,如例子②: j = 4,字符串的子串“caatc”,它的前缀表达式为{“c”,“ca”,“caa”,“caat”},后缀表达式为{“aatc”,“atc”,“tc”,“c”},因此由“caatc”的前缀后缀获得的部分匹配值为 “c”,对应的就是上面说的 0。
假设模式串 P = “caatcat”,目标字符串 T = “ctcaatcacaatcat”。
在第一轮匹配前,首先把模式字符串P与目标字符串T从各自第一个字符起对齐。
有第一轮结果可知,模式字符串与目标字符串在第2个字符处发生失配。检测到适配后本轮结束,目标指针不发生回溯,仍指向失配的位置。因为失配发生在第2个字符处,此时 j = 1。因此模式P在下一轮匹配时的起始地址为 pf(1-1)+1, 即P0。
在第二轮比较中,因为模式字符串P在的第1个字符处发生失配,此时 j = 0,因此让目标的指针前进一位,模式的起始比较地址回到p0。
发现模式字符串P中的第7个字符处发生失配,此时 j = 6。可知模式字符串P在下一轮匹配时的起始比较地址为pf(6-1)+1,即p2。目标指针一样不发生回溯,仍指向发生失配的位置。
通过第4轮比较后,匹配成功。经过简单分析,MP算法的时间复杂度大体为O(m+n),计算模式串的失效函数O(m),利用失效函数进行匹配O(n),m为模式串P的长度,n为目标串的长度。
1 /** 2 * MP算法的失效函数 3 * 4 * @param x 5 * @param m 6 * @param mpNext 7 */ 8 void preMp(char x[], int m, int mpNext[]) { 9 int i, j; 10 i = 0; 11 j = mpNext[0] = -1; 12 while (i < m) { 13 while (j > -1 && x[i] != x[j]) 14 j = mpNext[j]; 15 mpNext[++i] = ++j; 16 } 17 } 18 19 /** 20 * MP算法 21 * @param p 模式串 22 * @param t 目标串 23 */ 24 void mp(String p, String t) { 25 int m = p.length(); 26 int n = t.length(); 27 if (m > n) { 28 System.err.println("Unsuccessful match!"); 29 return; 30 } 31 32 char[] x = p.toCharArray(); 33 char[] y = t.toCharArray(); 34 35 int i = 0; 36 int j = 0; 37 int[] mpNext = new int[m+1]; 38 preMp(x, m, mpNext); 39 40 while (j < n) { 41 while (i > -1 && x[i] != y[j]) 42 i = mpNext[i]; 43 i++; 44 j++; 45 if (i >= m) { 46 System.out.println("Matching index found at: " + (j - i + 1)); 47 i = mpNext[i]; 48 } 49 } 50 }