参考博客:
1. 经典算法研究系列:六、教你初步了解KMP算法、updated
2. KMP算法的前缀next数组最通俗的解释,如果看不懂我也没辙了
字符串的匹配问题
假设文本是一个长度为n的数组T[1…n],模式是一个长度为m<=n的数组P[1….m]。
找出所有在文本T=“abcabaabcaabac”中的模式P=“abaa”所有出现。
该模式仅在文本中出现了一次,在位移s=3处。
简单的字符串匹配算法
循环, 对n-m+1个可能的每一个s值检查条件P[1….m]=T[s+1….s+m]。
运行时间为O((n-m+1) * m)
例: 目的字串target是banananobano,要匹配的字串pattern是nano
先将target和pattern串的第一个字符比较,如果相同就重复比较下一位,如果发现不同就将pattern整体右移一位,重新比较
时间浪费: 已经比较过的位置要重比一遍。如index =2,3,4,其中index=3的尝试是不必要的。
如果我们事先知道pattern本身的某些性质,可以不用每次匹配失败后都把index回退回去(pattern右移一位),在失配时直接把pattern移动到下一个可能的位置,就可以把根本不可能匹配的过程省略掉,kmp算法就是从此出发。
KMP算法
覆盖函数(overlay_function)
覆盖的定义
对序列a0a1…aj,找最大的k,使它满足
a0a1…ak=aj-kaj-k+1…aj,
即找到尽可能大的k,使pattern 前k字符与后k字符相匹配
覆盖函数含义
覆盖函数所表征的是pattern本身的性质,即pattern从左开始的所有连续子串的自我覆盖程度
比如如下的字串,abaabcaba
这里计数是从0开始的(也可以从1开始计数),其中-1表示没有覆盖
注意事项
如果选择较小的满足条件的k,那么当失配时,我们就会使pattern向右移动的位置变大,而较少的移动位置是存在匹配的,就会把可能匹配的结果丢失。
比如下面的序列,
在红色部分失配,正确的结果是k=1的情况,把pattern右移4位,如果选择k=0,右移5位则丢失了k=1的可能匹配结果。
计算
如果前一个字符i-1的next值是j,那么我们就把当前字符i与字串第j+1个字符进行比较
- 相等 当前字符i的next值为j+1, 即next(i) = j + 1 = next(i-1) + 1
不相等
- 前一个字符i-1的next值是0, 则当前字符的next值也为0
前一个字符i-1的next值不为0,则在前j个字符中找到h=next[j],如果pattern(h+1)==pattern(i),则next(i)=h,否则重复该过程
如(a g c t a g c )( a g c t a g c) t, 到最后一个c时next值为7,到t时不匹配需要重新求next值
t如果存在对称性,那么对称程度肯定比前面的c的对称程度(next值)小,要找更小的对称,必然在对称内部还存在子对称,而且这个t必须紧接着在子对称之后。如图所示
子对称存在的证明: 将两个agctagc分别设为T1,T2,有T1 = T2,假设存在更小的对称,T11 = T22,则T1中存在T12 = T22, T2中存在T21 = T11,则T11 = T12, T21 = T22, 说明必然有子对称。
故这里定位到T1末尾的c(index为j = 7),得知其next值为h = 3,且pattern(h+1) == pattern(i) == ‘t’,故当前字符t的next值为4。
示例代码如下,这里为从1开始计数
private int[] makeNext(String arr){
int len = arr.length();
int[] next = new int[len];
for(int i = 1, j = 0; i < len;){
if(arr.charAt(i) == arr.charAt(j)) next[i++] = ++j;
else if(j != 0) j = next[j-1];
else next[i++] = 0;
}
return next;
}
如”abaabcaba”的返回结果是0,0,1,1,2,0,1,2,3
KMP
当失配发生在j时,不用把index向回移动,index前面已经匹配过的部分在pattern自身就能体现出来,只要把pattern向右移动j-overlay(j)长度( 相当于将pattern_index改为overlay(j) )就可以了
如果失配时pattern_index==0,相当于pattern第一个字符就不匹配,这时应该把target_index加1,pattern向右移动1位。
老图了,有画错的地方,index=8和index=11处不应跳过,敬请见谅。
实战
Leetcode 28. Implement strStr()
Description
Implement strStr().
Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
(找出needle在haystack中最早出现的index,没有返回-1)
Solution
public class Solution {
public int strStr(String haystack, String needle) {
int m = haystack.length(), n = needle.length();
if(n == 0) return 0;
int[] next = makeNext(needle);
for (int i = 0, j = 0; i < m; ) {
if (haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
//字串匹配成功
if (j == n) return i - j;
}
else{
//pattern向右移动
if(j > 0) j = next[j-1];
//第一个字符就不匹配
else i++;
}
}
return -1;
}
//计算覆盖函数
private int[] makeNext(String arr){
int len = arr.length();
int[] next = new int[len];
for(int i = 1, j = 0; i < len;){
if(arr.charAt(i) == arr.charAt(j)) next[i++] = ++j;
else if(j != 0) j = next[j-1];
else next[i++] = 0;
}
return next;
}
}