KMP算法学习笔记
首先是一个字符串匹配问题
两个字符串,S P,求P是否为S的子串。S的长度为m,P的长度为n。也就是求一个L,S [ L , L+n ) = P。
示例:
T = [ a b a a c a b a b c a c]
P = [ a b a b c ]
-
暴力匹配
设两个指针,i指向S的开始,j指向P的开始。
如果 S [ i ] 和 P [ j ] 相同,则 i 和 j 同时向后移位(i++、j++)。判断是否相同,如果全部相同,匹配成功,输出结果。
如果不同,则 j 恢复到 0,i 加一,重复以上操作。
public static int indexOf(String S, String P){ int i = 0;//指向S 匹配起点 不要动 int j = 0;//指向P int sc = i;//临时扫描工具人 while(sc < s.length()){ if(s.charAt(sc) == p.charAt(j)){//字符相同 sc++; j++; if(j == p.length()){//P全部匹配成功 return i; }else{//匹配失败 i++; sc = i;//扫描指针以i为起点 j = 0;//j恢复至起点 } } } return -1; }
缺点:速度慢
-
KMP算法
前缀表 (prefix table)
写出P数组的前缀表 P = [ a b a b c ]
a
a b
a b a
a b a b
(不包括最后一个c)
把每一个前缀当作一个独立的字符串,然后依次求出最长公共前后缀列表。
求最长公共前后缀列表方法:
以 a b a b 为例,当前缀后缀长度为3时,前缀为a b a,后缀为 b a b。
两者不同,当长度为2时,前缀为 a b,后缀为 a b。两者相同,于是最长公共前后缀为2。
同理,依次求出,分别为 [ 0, 0, 1, 2 ]
next数组
最后在最前方加 -1, [ -1, 0, 0, 1, 2 ]为前缀表。长度与P长度相同,并一一对应。
P a b a b c next -1 0 0 1 2 next数组失配位置回退 j 的位置,是最长公共前后缀的长度。即最长公共前后缀列表的值。
f(L) = k 指当 L 失配,则 S 回溯到位置 K。K为P [ 0 ~ L-1] 的最长匹配数。
当字符串匹配时,i++,j++,如果不匹配,i不变,j 回溯,直到next[ j ] == -1,证明第一个也无法匹配,则i++。
这个方法的好处是,i永不回溯,j根据next数组回溯。
回溯之后,从回溯的位置开始比较,前面不比较。
private static int[] getNext(String p) { int len = p.length(); int[] next = new int[len]; next[0] = -1; int i = 0, k = -1; while (i < len - 1) { if (k == -1 || p.charAt(i) == p.charAt(k)) ++k; ++i; next[i] = k; } else { k = next[k]; } } return next; }
-
例题及完整代码
给出一个母串和一个子串,然后输出子串第一次出现在母串中的位置下标。
输入:
abaacababcac
ababc
输出:5
public class KMP { public static void main(String[] args) { // TODO Auto-generated method stub String T = "abaacababcac"; String P = "ababc"; int[] next = getNext(P); /*//测试next数组 * for(int i:next) { System.out.println(i); } */ System.out.println(kmp(T,P,next)); } private static int kmp(String T,String P,int[] next) { int i = 0,j = 0; while(i < T.length() && j < P.length()) { if(T.charAt(i) == P.charAt(j)) { i++; j++; }else { if(next[j] == -1) { i++; }else { j = next[j]; } } } if(j == P.length()) { return i - j; //题目要求输出位置下标,不用+1 } return -1; } //求next数组 private static int[] getNext(String p) { int len = p.length(); int[] next = new int[len]; next[0] = -1; int i = 0, k = -1; while (i < len - 1) { if (k == -1 || p.charAt(i) == p.charAt(k)) { ++k; ++i; next[i] = k; } else { k = next[k]; } } return next; } }