KMP算法
字符串匹配中,设主串指针为i,匹配串指针为j,则KMP算法是在匹配过程中,i不会回溯的算法。
next数组定义
维护一个next[j]数组,next[j] = 当j位置不匹配时,指针应该回溯到的位置,即s[i] != str[j]时,令j = next[j],再次判断s[i]和str[j]
next数组计算方法:
串的前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含最后一个字符的子串
当第j个字符匹配失败,由0~j-1个字符组成的串记为str[0..j-1],则
next[j] = str[0..j-1]的最长相等前后缀长度, next[0] = -1
next[j] == str[0…j-1]的最长相等前后缀长度证明
如上图,若已知从串为str,则主串和从串匹配到了图示位置,此时发生了匹配失败,设主串为s,则有s[i-j…i-1] == str[0…j-1],且s[i] != str[j], 若有next[j] = k,则
-
因为要求i不回溯,所以j回溯后,应该仍旧能和主串进行匹配,即有 s[i-k…i-1] == str[j-k…j-1] == str[0…k-1] , 其中,str[0…k-1]为前缀,str[j-k…j-1]为后缀,即满足要求的next[j]应该满足 是str[0…j-1]的相等前后缀的性质
-
在前后缀相同的情况下,应该尽量选取较大的k,即最长相等前后缀,保证不错过匹配的可能性(如s=“ABABABAC”, str = “ABABAC”,见下图)
因此,k = str[0…j-1]最长相等前后缀长度,即next[j] = str[0…j-1]最长相等前后缀长度。
next数组求解
- 令next[0] = -1
-
若已知next[0…j-1],求next[j],即求str[0…j-1]的最长相等前后缀
已知next[j-1] == k_j-1,设k = k_j-1(因为k_j-1不太好表示),即next[j-1] = k , 则有:
str[0..k-1] == str[j-k-1..j-2], 即 str[0..j-2]的 长度为k的前缀 == 长度为k的后缀, 且k为最长相等长度
1)若 str[k] == str[j-1], 则
str[0..k] == str[j-k-1..j-1], 即 str[0..j-1] 的 长度为k+1的前缀 == 长度为k+1的后缀
因此,有 next[j] = k + 1 = next[j-1] + 1;
2) 若str[k] != str[j-1], 则
与后缀相等的前缀必不是以k结尾,需要再往前找相等长度前后缀。
令k0 = next[k],则有:
str[0..k0-1] == str[k-k0..k-1], 即 str[0..k-1]的 长度为k0的前缀 == 长度为k0的后缀
又已知next[j-1] = k , 即str[0..k-1] == str[j-k-1..j-2]
由此可以得到,
str[0..k0-1] == str[k-k0..k-1] == str[j-k-1..j-k+k0-2] == str[j-k0-1..j-2]
再次判断,str[k0]是否与str[j-1]是否相等:
-
若str[k0] == str[j-1], 则next[j] = k0+1
str[0..k0] == str[j-k0-1..j-1], 即str[0..j-1]的 长度为k0+1的前缀 = 长度为k0+1的后缀
-
若 str[k0] != str[j-1],再令 k’ = next[k0],依次循环下去。
-
若k’ == -1, 则next[j] = 0
next数组求解代码
public int[] getNext(String str){
int n = str.length();
int[] next = new int[n];
next[0] = -1;
for(int j = 1;j < n;j++){
int k = next[j-1];
while (k >= 0 && str.charAt(k) != str.charAt(j-1)){
k = next[k];
}
next[j] = k + 1;
}
return next;
}