声明:文章内容根据龙书部分内容和网络上相关博客整理,当时随便写的笔记到现在时间太久远了,内容也不是很完善的样子……如果有未标明出处的地方希望大家指正,我会尽快更改
KMP算法
在目标串中匹配模式串的一个最简单、最直观的方法是一个个字符匹配,如果当前字符开始无法找到与模式串匹配的子串,那么向前移动一位,从该位置开始匹配模式串。但是在最坏的情况下,该算法的时间复杂度为
那么有没有什么更好的办法来匹配字符串呢?——KMP算法。与最原始的逐个匹配的办法不同,KMP算法在匹配失败时会将目标串的指针向后移动合适的位置,而不仅仅是一个位置,从而大大减少了比较的次数。
这一算法的主要思想还是在于串的重复特性,利用串的最大公共前后缀来实现:
设想目标串s和模式串p在目标串的i和j位置之间是匹配的,也就是说和是完全相同的,但是(即这两个串的下一位不匹配),那么这个时候要继续寻找下一个位置进行匹配。
那么下个位置是什么位置呢?假设两个字符串已经完全匹配的部分有最大公共前后缀:
这个时候只要把串p已经匹配部分的前缀和串s已经匹配部分的后缀对齐就可以了,这样就迅速找到了要继续匹配的位置。
next数组及其求解
既然已经知道KMP算法的基本思想,那么剩下的关键就是如何快速找到一个字符串的公共前后缀,这也是next数组要解决的问题。
假设有如下字符串:
该字符串对应的next数组中的值next[t]为该字符串从首字符开始长度为t+1(因为t从0开始取值)的子串的最长公共前后缀的长度。
对于上面的字符串而言,next数组的值如下:
| 对应的字串 | a | ab | aba | abab | ababa | ababaa | ababaaa |
|---|---|---|---|---|---|---|---|
| next | 0 | 0 | 1 | 2 | 3 | 1 | 1 |
那么,对于一般的字符串 ,其next数组应该如何求解?
很显然,当时,,这是一个初始条件。之后,当时,要判断子串的公共前后缀。这一点比较简单,直接判断是否等于即可。
现在,我们已经有了初始的条件,可以基于此初始条件不断往后递推。整个过程事实上包含了动态规划的思想。
现在假设我们有t和s两个指向字符串s的指针,s始终指向当前正在寻找最长公共前后缀的子串的末尾。
假设在上一步找寻最长公共前后缀的时候,t已经指向了该前缀的最后一个字符,s指向了对应子串的末尾。
这意味着子串和子串(长度为t)是完全相同的(公共前后缀)。接着开始寻找子串的最长公共前后缀。这时候,我们只要比较和是否相同即可,如果相同,很完美,最长的公共前后缀的长度加一,即: 要是不同,很显然,t要回溯来找到正确的前后缀。一种办法是t每次回溯一位,然后比较t的下一位和s的下一位是否相同。
整个过程大致如下:
t=t-1,再比较在新的t值下和是否相同。若相同:比较的下一位和的下一位是否相同,若结果为相同,t=t+1, next[s+1]=t,若不同,重复这一步;
但这种办法显得过于笨重,所以我们需要更好的办法来找到合适的回溯的位置。我们知道,根据上一次计算的结果,子串和子串是完全相同的,我们现在要做的,是在这两个子串内再找到合适的公共前后缀串。
怎么找呢?利用next[t],这之中就包含了长度为t的从头开始的子串的最长公共前后缀串的信息。利用next[t]找到的的前缀,恰恰好也是的后缀,它们完全相同。所以我们现在要回溯到的位置就是和。这个步骤就是快速回溯,找到正确的公共前后缀。在此基础上,再比较这两个子串后跟着的字符:如果这两个字符相同,那完美了,t=t+1, next[s+1]=t;如果不同,就再重复这个回溯的步骤。
使用java实现的next数组的计算
public static int[] calNext(char[] str) {
int[] next = new int[str.length];
// 第一个子串,即首个字符。其前缀为空串,表示为-1
next[0] = -1;
// t指示上一个子串的最长公共前后缀的最后一个字符在数组中的索引。初始为-1表示首字符前缀为空
int t = -1;
// s表示上一个子串的末尾。初始为0,表示第一个子串为首字符。
int s = 0;
for(; s < str.length - 1; s++) {
while ( t != -1 && (str[t+1] != str[s+1]) ) {
// 上个前缀的后一个字符和上个子串的后一个字符不匹配,
// 让t回溯,直到回到-1,表示没有匹配的前后缀串为止
t = next[t];
}
if ( str[t+1] == str[s+1] ) {
// t前进一位,表示找到了匹配的字符
t += 1;
// 更新next数组的值
next[s+1] = t;
} else {
// 没找到匹配的字符
next[s+1] = -1;
}
}
return next;
}