KMP算法Java实现
KMP算法简介
KMP算法是一种高效的字符串匹配算法,核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数已达到快速匹配的目的。通过一个next()函数实现,该函数包含了模式串的局部匹配信息,KMP算法的时间复杂度是
O(m+n)
。
算法步骤
- 寻找最大前缀后缀公共子串长度
对于P = p0 p1 …pj-1 pj,寻找模式串P中长度最大且相等的前缀和后缀。如果存在p0 p1 …pk-1 pk = pj-k pj-k+1…pj-1 pj,那么在包含pj的模式串中有最大长度为k+1的相同前缀后缀。
(1)以模式串ABAB为例,子串ABA有长度为1的相同前缀后缀A,子串ABAB有长度为2的相同前缀后缀AB(相同前缀后缀长度k+1为2)。
模式串 | A | B | A | B |
---|---|---|---|---|
最大前缀后缀公共子串长度 | 0 | 0 | 1 | 2 |
- 求next数组
next数组是除了当前字符外的最长相同前缀后缀,通过第一步得到最大长度后,将整体右移一位,然后初值赋值为-1。
模式串 | A | B | A | B |
---|---|---|---|---|
next数组 | -1 | 0 | 0 | 1 |
- 根据next数组进行匹配
匹配失败,j = next[j],模式串向右移动的位数为j - next[j]。也就是说模式串的后缀pj-k pj-k+1, …, pj-1 跟文本串si-k si-k+1, …, si-1匹配成功,但pj 跟si匹配失败时,因为next[j] = k,相当于在不包含pj的模式串中有最大长度为k 的相同前缀后缀,即p0 p1 …pk-1 = pj-k pj-k+1…pj-1,故令j = next[j],从而让模式串右移j - next[j] 位,使得模式串的前缀p0 p1 …pk-1 对应着文本串si-k si-k+1, …, si-1,而后让pk 跟si 继续匹配。
算法实例
public class KMP {public static void main(String[] args) {String str1 = "BBC ABCDAB ABCDABCDABDE";String str2 = "ABCDABD";//使用模式串获得next数组int[] next = getNext(str2);int index = kmp(str1, str2, next);System.out.println(index);}private static int kmp(String source, String pattern, int[] next) {char[] s = source.toCharArray();char[] p = pattern.toCharArray();int sLen = s.length;int pLen = p.length;for (int i=0,j=0;i<sLen;i++) {while(j>0 && s[i] != p[j]) {j = next[j-1];}if (s[i] == p[j]) {j++;}if (j == pLen) {return i-j;}}return -1;}//获取模式串的部分匹配表private static int[] getNext(String dest) {char[] p = dest.toCharArray();int pLen = p.length;int[] next = new int[pLen];for (int i=0;i<pLen;i++) {while(j>0 && p[i] != p[j]) {j = next[j-1];}if (p[i] == p[j]) {j++;}next[i] = j;}return next;}/*** 获得next数组* @param next next数组* @param t 模式串* @return int[] 返回数组*/private static void getNext(int[] next, String t){//对于模式串中每个元素tj,都存在一个实数k,//使得模式串t开头的k个字符(t0t1...tk-1)依次与tj前面的k个字符相同//如果这样的k有多个,则取最大的一个int j = 0, k = -1;next[0] = -1;char[] p = t.toCharArray();int len = p.length;while(j < len){if(k == -1 || p[j] == p[k]){j++;k++;next[j] = k; //如果p[j]与p[k]相同了,最大前后缀长度加1}else{k = next[k];}}}
}