一般思路
判断s中是否含有字符串t。
一般思路为:从s中首字符开始,依次与t中进行比对,直到t结尾或者某一个位置两者不同 。如果到t的结尾,则表示s中含有t。如果有一个位置不相同,那么从s中下一个字符开始,再次与t中字符比对。如下:
i = 0,j = 0;
for(;i<pl;i++){//pl为p字符串的长度,依次遍历它的每一个字符串
int start = i;//记录此时开始比较的字符串的下标
while(i<pl && j<len){//len子串的长度
if(p[i] == s[j]){
i++;
j++;
}else{
i = start;
j = 0;
break;
}
}
if(j >= len){
printf("%d ",start);
j = 0;
break;
}
}
这样的比较,每一次遇到不同的时候都需要从t串的第一个字符再次开始比较,因为最坏的时间复杂度为O(m*n)+——其中m,n分别表示s,t两个字符串的长度。KMP算法避免了每一次都从t串的第一个字符开始比较。
kmp思路
如下图红框部分——上面为s串,下面为t串:
图一
运行到红框位置时,t的下标为6,s的下标为8,s与t的字符不相同。但在此之前的字符串"RUQWPR",s与t是相同的。KMP算法正是利用了这一点对字符串匹配进行了优化。
对于字符串中的每一个字符(记下标为x),都会存在一截字符串,它的长度为n(可以为0),并且满足条件一:
t[0]...t[n-1]与t[x-n+1]...t[x]相同
其中t[x-n+1]...t[x]表示从下标为x(含)处往前数n个字符组成的字符串。
当找不到满足条件一的字符串时,记n为0,此时表示不存在相应的字符串;并且对字符串的首个字符来说,n也是0。我们可以将所有的n值存储于int[] next中。
对于某两个字符串的s与t来说,假设有s[i]!=t[j],那么下一次可直接进行判断s[i]与t[next[j-1]]是否相等。因为next数组保证了s[i](不含)前面的next[j-1]个字符组成的字符串与t[0]...t[next[j-1]-1]是完全相同的。例如ABCDGABCDH,当匹配到H时,源串的格式必定是...ABCDGABCD...,因此可以将ABCDGABCDH移到成如下格式:
下一步要匹配的就是G是否与s[i]相等,而G的下标必定是next[j-1]。
上述的移动就是KMP算法相对于普通匹配方法进行的优化地方。
next数组求解
代码如下:
private static void next(int[] n, String p) {
n[0] = 0;
for (int i = 1; i < p.length(); i++) {
int k = n[i - 1];//当前位置的前一个字符的n值
while (p.charAt(i) != p.charAt(k) && k > 0) {//k值会随着循环的变化而不断变小,当k变为0时,会出现异常,所以加个k>0
k = n[k - 1];
}
if (p.charAt(i) == p.charAt(k)) {
n[i] = k + 1;
} else {
n[i] = k;
}
}
}
具体的求解思路与kmp思路这一节完全一样。
匹配代码
其具体思路与next数组求解一样。都是在某一个字符不匹配时,将模板字符移动到next[pi-1]。private static boolean kmp(String s, String p) {
int[] n = new int[p.length()];
next(n, p);//求解next数组
int pi = 0;//p的下标
for (int x = 0; x < s.length() && pi < p.length(); ) {
if (s.charAt(x) == p.charAt(pi)) {//如果s与p中当前字符相同,则进行下一个字符的比较
x++;
pi++;
if (pi >= p.length()) {
out("index = "+(x-p.length()));//此处可以获取p在s中的下标
return true;
}
continue;
}
if (pi == 0) {//p中的第一个字符都匹配不成功,则需要从s的下一个字符开始匹配
x++;
}else {//某一个字符时s与p不相同,则p中下一个要匹配的字符下标为n[pi-1]
pi = n[pi - 1];
}
}
return false;
}