KMP算法详解及Java实现
- KMP算法描述
- 部分匹配算法原理
- 部分匹配算法实现(Java)
- KMP匹配算法
- Java实现
- 以前在学习计算机数据结构时,涉及到基础算法KMP算法,学习了好几次,在网上找了很多资料,说得也不是很清楚,后来在看了阮一峰老师的博客后,终于理解了。
KMP算法描述
说起KMP算法,就不得不说字符串匹配,最初的字符串匹配效率并不高,但是为了解决提高字符串匹配效率,Knuth-Morris-Pratt算法(简称KMP)它以三个发明者命名,起头的那个K就是著名科学家Donald Knuth。 这个算法提高效率的核心就是部分匹配算法,要想理解KMP算法,关键就是要理解部分匹配算法,下面详细说明一下部分匹配算法及原理。
部分匹配算法原理
部分匹配算法关键是生成部分匹配表,要计算出部分匹配表,需要计算出子串中每个字符的部分匹配值,首先需要搞清楚以下几个基本概念,前缀、后缀和部分匹配值。
- 前缀:指除去最后一个字符的子串的全部头部组合;
- 后缀:指除去第一个字符的子串的全部尾部组合;
- 部分匹配值: 就是匹配子串中的每个子串的前缀和后缀的最长的共有元素的最大长度。
下面举例说明,以ABCDABD为例(后面也会以这个字符串为例解释KMP算法):
ABCDABD所有可能匹配的子串组合有{A, AB, ABC, ABCD, ABCDA, ABCDAB, ABCDABD}
– A的前缀和后缀都为{},共有元素的长度为0;
– AB的前缀为{A},后缀为{B},共有元素的长度为0;
– ABC的前缀为{A, AB},后缀为{BC, C},共有元素的长度0;
– ABCD的前缀为{A, AB, ABC},后缀为{BCD, CD, D},共有元素的长度为0;
– ABCDA的前缀为{A, AB, ABC, ABCD},后缀为{BCDA, CDA, DA, A},共有元素为A,长度为1;
– ABCDAB的前缀为{A, AB, ABC, ABCD, ABCDA},后缀为{BCDAB, CDAB, DAB, AB, B},共有元素为AB,长度为2;
– ABCDABD的前缀为{A, AB, ABC, ABCD, ABCDA, ABCDAB},后缀为{BCDABD, CDABD, DABD, ABD, BD, D},共有元素的长度为0。
部分匹配的实质是找出字符串头部和尾部重复的最大子串的长度。比如,ABCDAB之中有两个AB,那么ABCDAB的部分匹配值就是2(AB的长度)。匹配位置移动的时候,第一个AB向后移动4位(字符串长度-部分匹配值),就可以来到第二个AB的位置。
部分匹配算法实现(Java)
/**
* 初始化部分匹配数组
* @param matchStr
* @return
*/
public static int[] initNext(String matchStr) {
int[] next = { -1 };
if (strIsValid(matchStr)) {
int matchLen = matchStr.length();
next = new int[matchLen];
next[0] = -1;
int head = 0;
int tail = -1;
while (head < matchLen - 1) {
if (tail == -1 || matchStr.charAt(head) == matchStr.charAt(tail)) {
tail += 1;
head += 1;
next[head] = tail;
} else {
tail = next[tail];
}
}
}
return next;
}
public static boolean strIsValid(String str) {
return str != null && !str.trim().isEmpty();
}
计算部分匹配表过程
初始化各变量值:
head: 0 matchStr[head]=A next[head]=-1
tail: -1
next: [-1]
第1次运行结束后各变量值:
head: 1 matchStr[head]=B next[head]=0
tail: 0 matchStr[tail]=A next[tail]=0
next: [-1,0]
第2次运行结束后各变量值:
head: 1 matchStr[head]=B next[head]=0
tail: -1
next: [-1,0]
第3次运行结束后各变量值:
head: 2 matchStr[head]=C next[head]=0
tail: 0 matchStr[tail]=A next[tail]=0
next: [-1,0,0]
第4次运行结束后各变量值:
head: 2 matchStr[head]=C next[head]=0
tail: -1
next: [-1,0,0]
第5次运行结束后各变量值:
head: 3 matchStr[head]=D next[head]=0
tail: 0 matchStr[tail]=A next[tail]=0
next: [-1,0,0,0]
第6次运行结束后各变量值:
head: 3 matchStr[head]=D next[head]=0
tail: -1
next: [-1,0,0,0]
第7次运行结束后各变量值:
head: 4 matchStr[head]=A next[head]=0
tail: 0 matchStr[tail]=A next[tail]=0
next: [-1,0,0,0,0]
第8次运行结束后各变量值:
head: 5 matchStr[head]=B next[head]=1
tail: 1 matchStr[tail]=B next[tail]=1
next: [-1,0,0,0,0,1]
第9次运行结束后各变量值:
head: 6 matchStr[head]=D next[head]=2
tail: 2 matchStr[tail]=C next[tail]=2
next: [-1,0,0,0,0,1,2]
KMP匹配算法
KMP的详细过程再此处不再详细展开,简言之,KMP算法就是在部分匹配的基础上快速找到下一个开始匹配的位置,而不是逐步移动到下一个位置
Java实现
/**
* 找出匹配的位置
* @param str
* @param matchStr
* @return
*/
public static int findMatchPosition(String str, String matchStr) {
if (!(strIsValid(str) && strIsValid(matchStr))) {
return -1;
}
int sIndex = 0;
int mIndex = 0;
int[] next = initNext(matchStr);
while (sIndex < str.length() && mIndex < matchStr.length()) {
if (str.charAt(sIndex) == matchStr.charAt(mIndex)) {
sIndex += 1;
mIndex += 1;
} else if (next[mIndex] == -1) {
sIndex++;
} else {
mIndex = next[mIndex];
}
}
return mIndex == matchStr.length() ? (sIndex - mIndex) : -1;
}
测试代码
public static void main(String[] args) {
String str = "BBC ABCDAB ABCDABDABDE";
String match = "ABCDABD";
System.out.println(findMatchPosition(str, match));
System.out.println(findMatchPosition("abc", "ab"));
System.out.println(findMatchPosition("abcabd", "abd"));
System.out.println(findMatchPosition("abcabdf", "f"));
}
结果输出
11
0
3
6