改进的模式匹配算法
KMP算法的优点:主串不回溯。
KMP算法的时间复杂度为O(m+n)
字符串的前缀:除最后一个字符以外,所有头部子串。
字符串的后缀:除第一个字符以外,所有尾部子串。
PM数组是部分匹配值,部分匹配值是字符串的前缀和后缀的最长相等前后缀长度。
以abca举例:
‘a’,前后缀均为空集,即最长相等前后缀长度为0;
‘ab’,前缀是{ a },后缀是{ b },最长相等前后缀长度是0;
‘abc’,前缀是{ a, ab },后缀是{ c, bc },最长相等前后缀长度是0;
‘abca’,前缀是{ a, ab, abc },后缀是{ a, ca, bca },最长相等前后缀长度是1;
表1 PM表
编号 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
T | a | b | c | a |
PM | 0 | 0 | 0 | 1 |
我们使用部分匹配值(PM)主要是为了求解移动位数。
移动位数 = 已匹配的字符数 ➖ 对应的部分匹配值
而next数组是由PM数组右移然后加一所得。
表2 next表
编号 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
T | a | b | c | a |
next | 0 | 1 | 1 | 1 |
public class StringMatching {
//计算next数组
static void get_next(char T[], int next[]) {
int i=1,j=0;
while(i<T.length) {
if(j == 0 || T[i] == T[j]) {
i++;
j++;
next[i]=j;
}else {
j=next[j];
}
}
}
//KMP匹配算法
static int Index_KMP(char S[], char T[], int next[]) {
int i=1,j=1;
//get_next(T,next);
while(i<S.length && j<T.length) {
if(j == 0 || S[i] == T[j]) {
i++;j++;
}else
j=next[j];
}
if(j>=T.length)
return i-T.length;
else
return 0;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
String S = "cdababcadfgh";
String T = "abca";
int next[]=new int[20];//初始化next数组
get_next(T.toCharArray(),next); //next值
System.out.println("next数组:");
for(int i=1;i<=T.length();i++) {
System.out.print(next[i]+" ");
}
System.out.println();
int id = Index_KMP(S.toCharArray(),T.toCharArray(),next);
if(id == 0) {
System.out.print("匹配失败!");
}else {
System.out.print("匹配成功!字符串T在字符串S的第"+id+"的位置。");
}
}
}
其实一般情况下,暴力匹配的实际执行时间近似为KMP,因此暴力匹配至今仍被采用,但在主串与子串有许多“部分匹配”的时候,KMP算法就明显速度快于暴力匹配算法。