KMP算法是无回溯的字符串匹配算法
此算法的关键是求next(j);
若设在进行某一趟匹配比较时在模式P的第j位失配,
- 如果j>0,那么在下一趟比较时模式串P的起始比较位置是next[j],目标串T的指针不回溯,仍指向上一趟失配的字符;
- 如果j=0,则目标串指针T进一,模式串指针P回到 p0 p 0 ,继续进行下一趟匹配比较。
知道了next数组怎么使用后,再介绍KMP算法的原理。
朴素模式匹配速度慢的原因是有回溯,而这些回溯是可以避免的。例如:
T a b b a b a
P a b a
第一趟比较,t0 = p0,t1=p1,t2 ≠ ≠ p2,但p0 ≠ ≠ p1,由此可推知t1(=p1) ≠ ≠ p0,将P右移一位,用t1和p0比较肯定不等,这一趟可以不比较。又由于p0=p2,所以t2 ≠ ≠ p0 (p0=p2),将P再右移一位,用t2和p0比较也不会相等。我们应该将P直接右移3位。从t3和p0开始进行比较。这样的匹配过程对于T来说就消除了回溯。
一般情形
如果有从目标T的第s个位置 ts t s 与模式P的第0个位置 p0 p 0 开始进行比较,直到在目标T第s+j位置 ts+j t s + j ”失配”,这时应有:
tsts+1ts+2...ts+j−1=p0p1p2...pj−1 t s t s + 1 t s + 2 . . . t s + j − 1 = p 0 p 1 p 2 . . . p j − 1
按朴素模式匹配算法,下一趟应从目标T的第s+1个位置起用 ts+1 t s + 1 于模式P中的 p0 p 0 对其,重新开始匹配比较。若想匹配,必须满足:
ts+1ts+2ts+3...ts+j...ts+m=p0p1p2...pj−1...pm−1 t s + 1 t s + 2 t s + 3 . . . t s + j . . . t s + m = p 0 p 1 p 2 . . . p j − 1 . . . p m − 1
如果在模式串P中,
p0p1p2...pj−2≠p1p2...pj−1 p 0 p 1 p 2 . . . p j − 2 ≠ p 1 p 2 . . . p j − 1
则第s+1趟不用进行匹配比较,就能断定它必然“失配”。
依此类推,直到对于某一个值k,使得:
p0p1p2...pk+1≠pj−k−2pj−k−1...pj−1 p 0 p 1 p 2 . . . p k + 1 ≠ p j − k − 2 p j − k − 1 . . . p j − 1
且 p0p1p2...pk=pj−k−1pj−k...pj−1 p 0 p 1 p 2 . . . p k = p j − k − 1 p j − k . . . p j − 1
才有 p0p1p2...pk=ts+j−k−1ts+j−k...ts+j−1 p 0 p 1 p 2 . . . p k = t s + j − k − 1 t s + j − k . . . t s + j − 1
这样,可以把在某趟比较“失配”时的模式P从当前位置直接向右滑动 j-k-1位。
可以从T中的 ts+1 t s + 1 (即上一趟失配的位置)与模式串的 pk+1 p k + 1 开始,继续向下进行比较。
next特征函数
用一个next特征函数来确定:当模式P中的第j个字符与目标串T中相应的字符失配时,模式P中应当由哪个字符(设为第k+1个)与目标中刚失配的字符重新继续进行比较。
- 计算next(j)的算法
public static int[] next(String pat) {
int[] next = new int[pat.length()];
int j = 0, k = -1;
next[0] = -1;
while (j < pat.length()-1) {
if (k == -1 || pat.charAt(j)==pat.charAt(k)) {
j++;
k++;
next[j] = k;
}else {
k = next[k];
}
}
return next;
}
- KMP算法
public static int KMPMatch(String tar,String pat) {
int[] next= new int[pat.length()];
int p = 0;
int t = 0;
int plen = pat.length();
int tlen = tar.length();
next = next(pat);
while (p < plen && t < tlen) {
if (p == -1 || pat.charAt(p) == tar.charAt(t)) {
p++;
t++;
}
else p = next[p];
}
if (p < plen) return -1;
else return t-plen;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String tar = sc.nextLine();
String pat = sc.nextLine();
System.out.println("KMP匹配结果:"+KMPMatch(tar, pat));
}
- 运行结果
acabaabaabcacaabc
abaabcac
KMP匹配结果:5
next数组为:
-1 0 0 1 1 2 0 1
总结:KMP为无回溯的算法,在计算next过程中,实际上也是一个模式匹配的过程,只是目标串和模式串现在是同一个串P,也是无回溯的过程。KMP算法的时间复杂度为O(lengthT+lengthP)。