KMP算法(看毛片算法)是一种改进的字符串匹配算法
KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)
kmp算法中最总要的是next数组,然后什么是next数组呢?
next数组就是优化“看毛片算法”的辅助数组,没有next数组kmp的时间复杂度就变成O(m*n),所以next数组是kmp的重点。
next数组是把在字符串里的当前字符的前缀与后缀的最长重复个数。
比如字符串为“aabaaabaa”next数组为:
下标 0 1 2 3 4 5 6 7 8 模式串 a a b a a a b a a next数组 0 1 0 1 2 2 3 1 2
怎么求得next数组呢?
就是两个指针先是i指针在0位置,j指针在-1位置上然后向前移动,如果不懂请看一下下面的代码:
private static int[] getNext(String f) {
int next[] = new int[f.length() + 1];//next数组
int j = -1;
int i = 0;
next[0] = -1;
while (i < f.length()) {
if (j == -1 || f.charAt(i) == f.charAt(j)) {//如果j=-1说明上一次已经执行过j=next[j]了,next数组到头了,不能再退了所以需要向前走
i++;
j++;
next[i] = j;//next数组赋值
} else {
j = next[j];//回溯到上一层
}
}
return next;
}
一般我喜欢把next数组扩大一位,然后第一位也就是next[0]定义为-1,然后next数组里的数向后移动一位。
比如:aaaab的next数组为[0, 1, 2, 3, 0],然后我喜欢把它变成[-1, 0, 1, 2, 3, 0](这个肯定有根据的。)
求完next数组,我们该进去正题:求kmp了。
kmp算法
也是两个指针 i , j 指针 i 在主串上 , j 在模式串上,如果主串上的字符和模式串上的字符相同时,i 和 j 同时向前移动一个位置,也就是说 i++,j++ ;然后如果不相同 i 不动 j = next [ j ] ,就是去next数组里找前缀相同的地方。
直到 i ==(主串长度-1),或者满足条件,循环结束。如果看不太懂请看下面的代码:
private static int kmp(String s, String f) {
int next[] = getNext(f);//得到上面代码的next数组
int i = 0;
int j = 0;
while (i < s.length()) {
if(j==-1||s.charAt(i)==f.charAt(j)) {
i++;
j++;
}else {
j=next[j];
}
if (j == f.length()) {
return i-j+1;//这一行根据题意写代码
}
}
return -1;
}
只看代码是不行的需要做题来巩固!!!
最重要的事情说三遍!!
下面我推荐几道kmp的基础题:
杭电上的1711题,刚刚上面那两个代码就是这个代码,这个题是个模板题,上面两个代码不同的就是把字符串换成数组,俗话说“换汤不换药”,这个代码就不发了。
hdu1358 Problem - 1358
题意:给出一个字符串求出每个前缀的最小周期
这个题需要研究下next数组,与kmp没有半毛钱关系。
一看就知道 t=(len-next[len]) 这个关系。代码如下:
static int r=1;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int n = sc.nextInt();
if(n==0)break;
String s=sc.next();
int next[]=getNext(s);
System.out.println("Test case #"+(r++));
for (int i = 1; i <=n; i++) {
if(next[i]!=0&&i%(i-next[i])==0) {
System.out.println(i+" "+i/(i-next[i]));
}
}
System.out.println();
}
}
HDU3336 Problem - 3336
题意:输入一个字符串求每个前缀在串中出现的次数和
题目比较简单,直接上代码:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int n = sc.nextInt();
for (int i = 0; i < n; i++) {
int t=sc.nextInt();
String s = sc.next();
int next[]=getNext(s);
int sum=(next[s.length()]%10007+s.length()%10007)%10007;
for (int j = 0; j <s.length(); j++) {
if(next[j]>0&&next[j]+1!=next[j+1])
sum=(sum+next[j])%10007;
}
System.out.println(sum%10007);
}
}
}
hdu2087 Problem - 2087
题意:给出主串和模式串,看模式串在主串出现几次,主串的元素不同重复利用。
裸KMP从前向后扫一遍kmp就好了,代码如下:
import java.util.Scanner;
public class Main{
static int r=1;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String s=sc.next();
if(s.equals("#"))break;
String f=sc.next();
System.out.println(kmp(s,f));
}
}
private static int kmp(String s, String f) {
int sum = 0;
int next[] = getNext(f);
int i = 0;
int j = 0;
while (i < s.length()) {
while (j != -1 && s.charAt(i) != f.charAt(j)) {
j = next[j];
}
i++;
j++;
if (j == f.length() ) {
sum++;
j=0;
}
}
return sum;
}
private static int[] getNext(String f) {
int next[]=new int[f.length()+1];
int j = -1;
int i = 0;
next[0] = -1;
while (i < f.length() ) {
if (j == -1 || f.charAt(i) == f.charAt(j)) {
i++;
j++;
next[i] = j;
} else {
j = next[j];
}
}
return next;
}
}
下面也有几道也是板子题,我就不一一给出代码了都是简单题,有空好好研究一下 next (这个是核心)
推荐几道题(都是简单题):
poj2406 2406 -- Power Strings
这个题是给定一个串求出串的最小周期:len/(len-next[len])
poj2752 2752 -- Seek the Name, Seek the Fame
题意:给定一个串求出满足既是前缀又是后缀的串的起始位置
poj2185 2185 -- Milking Grid
题意:输入一个矩阵由字符组成,求出矩阵的最小组成单位。
poj3461 3461 -- Oulipo
题意:找出第一个字符串在第二个字符串中出现次数。