KMP算法JAVA代码实现
“KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。
KMP思路:
1.先得到子串的部分匹配表;
2.使用部分匹配表完成KMP算法;
1.部分匹配表,
把子串的每一个前缀当成一个独立的字符串,对于每一个字符串来找出它的最长公共前后缀的长度,组成一张表,称为部分匹配表.
我们用一个字符串(ABABCABAA)举个例子:
用 ABABCABA 举个例子吧:
它的前缀有:
A
AB
ABA
ABAB
ABABC
ABABCA
ABABCAB
它的后缀有:
A
BA
ABA
CABA
BCABA
ABCABA
BABCABA
所以它的公共前后缀是ABA,长度为3,所以部分匹配表对应的值就为3.
对应的代码实现:
//获取到一个字符串(子串)的部分匹配值的表
public static int[] kmpNext(String dest) {
//先将字符串转化为char数组;
char pattern[] = dest.toCharArray();
int n = pattern.length;//n代表数组的长度
//创建一个部分匹配值的表,长度和传入的字符串的长度相同
int prefix[] = new int[dest.length()];
//默认pattern[0]=0;
prefix[0] = 0;
int len = 0;
int i = 1;
while (i < n) {
//System.out.println(i + "|||||" + len);
if (pattern[i] == pattern[len]) {
len++;
prefix[i] = len;
i++;
} else {
if (len > 0) {
len = prefix[len - 1];
} else {
prefix[i] = 0;
i++;
}
}
}
return prefix;
}
然后将得到的表向后移动一位,让prefix[0] = -1;
public static void move_pattern(int prefix[]) {
for (int i = prefix.length - 1; i >= 1; i--) {
prefix[i] = prefix[i - 1];
}
prefix[0] = -1;
}
写一个main方法进行测试:
public static void main(String[] args) {
String a = "ABABCABAA";
int[] prefex = kmpNext(a);
System.out.println("原部分匹配表:" + Arrays.toString(prefex));
move_pattern(prefex);
System.out.println("移动后的部分匹配表:" + Arrays.toString(prefex));
}
结果如下:
2.使用部分匹配表完成KMP算法:
//haystack(父串) needle(子串)
public static int kmpSearch(String haystack, String needle) {
char father[] = haystack.toCharArray();
char son[] = needle.toCharArray();
int prefex[] = kmpNext(needle);
move_pattern(prefex);
int m = father.length; //m为父串的长度
int n = prefex.length; //n为子串的长度
int i = 0;
int j = 0;
while (i < m) {
if (j == n - 1 && father[i] == son[j]) {
return i - j;
//如果继续匹配的话应该要:
// j = prefex[j];
}
if (father[i] == son[j]) {
i++;
j++;
} else {
j = prefex[j];
if (j == -1) {
i++;
j++;
}
}
}
return -1; //如果没有匹配到返回-1
}
全部代码:
public class KMP {
/*
KMP思路:
1.先得到子串的部分匹配表;
2.使用部分匹配表完成KMP算法;
*/
//获取到一个字符串(子串)的部分匹配值的表
public static int[] kmpNext(String dest) {
//先将字符串转化为char数组;
char pattern[] = dest.toCharArray();
int n = pattern.length;//n代表数组的长度
//创建一个部分匹配值的表,长度和传入的字符串的长度相同
int prefix[] = new int[dest.length()];
//默认pattern[0]=0;
prefix[0] = 0;
int len = 0;
int i = 1;
while (i < n) {
//System.out.println(i + "|||||" + len);
if (pattern[i] == pattern[len]) {
len++;
prefix[i] = len;
i++;
} else {
if (len > 0) {
len = prefix[len - 1];
} else {
prefix[i] = 0;
i++;
}
}
}
return prefix;
}
public static void move_pattern(int prefix[]) {
for (int i = prefix.length - 1; i >= 1; i--) {
prefix[i] = prefix[i - 1];
}
prefix[0] = -1;
}
//haystack(父串) needle(子串)
public static int kmpSearch(String haystack, String needle) {
char father[] = haystack.toCharArray();
char son[] = needle.toCharArray();
int prefex[] = kmpNext(needle);
move_pattern(prefex);
int m = father.length; //m为父串的长度
int n = prefex.length; //n为子串的长度
int i = 0;
int j = 0;
while (i < m) {
System.out.println(i + "||||" + j);
if (j == n - 1 && father[i] == son[j]) {
return i - j;
//如果继续匹配的话应该要:
// j = prefex[j];
}
if (father[i] == son[j]) {
i++;
j++;
} else {
j = prefex[j];
if (j == -1) {
i++;
j++;
}
}
}
return -1;
}
}
力扣第28题测试结果如下: