KMP算法学习笔记
参考1简书:https://www.jianshu.com/p/18410598c061(DFA_KMP)参考2:https://segmentfault.com/a/1190000008575379(BruteForce、KMP)参考2原文:https://subetter.com/algorithms-and-mathematics/kmp-algorithm.html ...
KMP原理版,动画分析,易懂:https://www.bilibili.com/video/av49930100?from=search&seid=1592349894888831761
- BruteForce(暴力算法)
- DFA_KMP(使用有限状态自动机)
- KMP1(使用next数组,未优化)
- KMP2(使用next数组,优化后)
表1 4种模式匹配算法的平均O(n)比较

因为测试数据太少,可能存在偶然情况,所以BruteForce竟然和KMP比较接近。但是可以看到,DFA_KMP和优化后的KMP明显快。

图1 4种模式匹配算法的平均O(n)比较
我对同组用例用4种算法测试,跑了10次。去掉最大、最小值后取了平均值。这个图就很清晰了,DFA在此次测试中是最稳定的,其次是优化后的KMP。

图2 4种模式匹配算法的平均O(n)比较
什么是KMP?
leetcode编程原题:Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
定义:给定一个主串(以 S 代替)和模式串(以 P 代替),要求找出 P 在 S 中出现的位置,此即串的模式匹配问题。
Knuth-Morris-Pratt 算法(简称 KMP)是解决这一问题的常用算法之一,这个算法是由高德纳(Donald Ervin Knuth)和沃恩·普拉特在1974年构思,同年詹姆斯·H·莫里斯也独立地设计出该算法,最终三人于1977年联合发表。
BruteForce是什么?
bruteforce即朴素匹配算法,也叫暴力匹配。O(n)=(n*m) n为主串长度,m为模式串长度。
用模式串从第一位和主串开始匹配,对应位置相等则,i++,j++,主串,子串都向后移位,继续匹配下一位。
对应位置不相等,则主串指针返回到第二位(主串下标从1开始),模式串则重新从开始第1位和主串匹配,重复上述过程。直到,模式串每位都和主串匹配上。
可见,这种暴力算法每次不匹配时,都要回溯到开始,而不匹配处之前的串是相匹配的,一旦回溯,也就意味着,已经匹配过的位置会重复匹配,时间就是在这个过程中浪费的。
例:主串txt:abababcdf
模式串pat:abcd
高纳德大神的KMP又是什么呢?
请移步,参考引文1……因为太长了……-_-
改进的KMP又改进了哪些地方呢?
在这之前,你需要理解,前缀、后缀、next数组这些概念。请移步参考引文2。
摘自参考引文:
KMP算法(未优化版): next数组表示最长的相同真前后缀的长度,我们不仅可以利用next来解决模式串的匹配问题,也可以用来解决类似字符串重复问题等等,这类问题大家可以在各大OJ找到,这里不作过多表述。
KMP算法(优化版): 根据代码很容易知道(名称也改为了nextval),优化后的next仅仅表示相同真前后缀的长度,但不一定是最长(称其为“最优相同真前后缀”更为恰当)。此时我们利用优化后的next可以在模式串匹配问题中以更快的速度得到我们的答案(相较于未优化版),但是上述所说的字符串重复问题,优化版本则束手无策。
看不懂代码怎么办?
先理解思想,最起码能手写BruteForce。DFA_KMP,KMP及优化后的KMP,先理解思想。
详情移步引文1、2,分别讲述了DFA_KMP、KMP及优化后KMP的详细过程。
另有引文3知乎大神,通俗讲解。
还有引文4印度小哥(双语版,保证听得懂),视频讲解。
ps:视频中,next数组没有向后移位1位,参考引文2,你就会知道移位是什么意思,在代码中是怎样的实现。
以下代码为java版
**先来朴素算法,也就是暴力算法**
1、BruteForce
public class BruteForce {
public static void main(String[] args) {
String pat = "abcdabd";
String txt = "bbc abcdab abcdabcdabde";
long startTime1 = System.nanoTime(); //获取开始时间
int loc =search(pat, txt);
long endTime1 = System.nanoTime(); //获取结束时间
if(loc==txt.length())
System.out.println("not found");
else
System.out.println("text: " + txt);
System.out.print("pattern: ");
for (int i = 0; i < loc; i++)
System.out.print(" ");
System.out.println(pat);
System.out.println("loc:" + loc);
System.out.println("使用BruteForce,程序运行时间:"
+ (endTime1 - startTime1) + "ns"); //输出程序运行时间
}
public static int search(String pat,String txt) {
int m = pat.length();
int n = txt.length();
for(int i=0;i<n-m;i++) {
int j;
for(j=0;j<m;j++) {
if(txt.charAt(i+j)!=pat.charAt(j)) {
break;
}
}
if(j==m)return i; //found
}
return n;//not found
}
}
text: bbc abcdab abcdabcdabde
pattern: abcdabd
loc:15
使用BruteForce,程序运行时间:11842ns
2、使用DFA有限状态自动机实现
public class DFAKMP {
private final int R; // the radix
private int[][] dfa; // the KMP automoton
private String pat; // the pattern string
public static void main(String[] args) {
String pat = "abcdabd";
String txt = "bbc abcdab abcdabcdabde";
DFAKMP kmp1 = new DFAKMP(pat);
long startTime1 = System.nanoTime(); //获取开始时间
int offset = kmp1.search(txt);
long endTime1 = System.nanoTime(); //获取结束时间
// print results
System.out.println("text: " + txt);
System.out.print("pattern: ");
for (int i = 0; i < offset; i++)
System.out.print(" ");
System.out.println(pat);
System.out.println("loc:"+offset);
+ (endTime1 - startTime1) + "ns"); //输出程序运行时间
}
public int search(String txt) {
// simulate operation of DFA on text
int m = pat.length();
int n = txt.length();
int i, j;
for (i = 0, j = 0; i < n && j < m; i++) {
j = dfa[txt.charAt(i)][j];
}
if (j == m) return i - m; // found
return n; // not found
}
public DFAKMP(String pat) {
this.R = 256;
this.pat = pat;
// build DFA from pattern
int m = pat.length();
dfa = new int[R][m];
dfa[pat.charAt(0)][0] = 1;
for (int x = 0, j = 1; j < m; j++) {
for (int c = 0; c < R; c++)
dfa[c][j] = dfa[c][x]; // Copy mismatch cases.
dfa[pat.charAt(j)][j] = j+1; // Set match case.
x = dfa[pat.charAt(j)][x]; // Update restart state.
}
}
}
text: bbc abcdab abcdabcdabde
pattern: abcdabd
loc:15
使用DFA_KMP,程序运行时间:5131ns
3、使用next数组,KMP,未优化
public class KMP {
public static void main(String[] args) {
int next[] = new int[100];
int nextVal[] = new int[100];
String pat = "abcdabd";
String txt = "bbc abcdab abcdabcdabde";
long startTime1 = System.nanoTime(); //获取开始时间
int offset = KMP(txt, pat, next);
long endTime1 = System.nanoTime(); //获取结束时间
System.out.println("text: " + txt);
System.out.print("pattern: ");
for (int i = 0; i < offset; i++)
System.out.print(" ");
System.out.println(pat);
System.out.println("loc:" + offset);
System.out.println("使用未优化KMP,程序运行时间:"
+ (endTime1 - startTime1) + "ns"); //输出程序运行时间
System.out.println();
}
//未优化KMP
public static void getNext(String pat,int next[]) {
int m = pat.length();
int i = 0; // P 的下标
int j = -1;
next[0] = -1;
while (i < m - 1)
{
if (j == -1 || pat.charAt(i) == pat.charAt(j))
{
i++;
j++;
next[i] = j;
}
else
j = next[j];
}
}
/*Return the index of the first occurrence of pat in txt,
or -1 if pat is not part of txt. */
static int KMP(String txt, String pat, int next[])
{
getNext(pat, next);
int i = 0; // the index of txt
int j = 0; // the index of pat
int n = txt.length();//txt length
int m = pat.length();//pat length
while (i < n && j < m)
{
if (j == -1 || txt.charAt(i) == pat.charAt(j)) // P 的第一个字符不匹配或 S[i] == P[j]
{
i++;
j++;
}
else
j = next[j]; // 当前字符匹配失败,进行跳转
}
if (j == m) // 匹配成功
return i - j;
return -1;
}
}
text: bbc abcdab abcdabcdabde
pattern: abcdabd
loc:15
使用未优化KMP,程序运行时间:12632ns
4、使用next数组,KMP,优化后
public class KMP {
public static void main(String[] args) {
int next[] = new int[100];
int nextVal[] = new int[100];
String pat = "abcdabd";
String txt = "bbc abcdab abcdabcdabde";
long startTime2 = System.nanoTime(); //获取开始时间
int offset2 = KMP(txt, pat, nextVal);
long endTime2 = System.nanoTime(); //获取结束时间
System.out.println("text: " + txt);
System.out.print("pattern: ");
for (int i = 0; i < offset2; i++)
System.out.print(" ");
System.out.println(pat);
System.out.println("loc:" + offset2);
System.out.println("使用优化KMP,程序运行时间:"
+ (endTime2 - startTime2) + "ns"); //输出程序运行时间
}
/* 优化后KMP*/
void getNextval(String pat, int nextval[])
{
int m = pat.length();
int i = 0; // P 的下标
int j = -1;
nextval[0] = -1;
while (i < m - 1)
{
if (j == -1 || pat.charAt(i) == pat.charAt(j))
{
i++;
j++;
if (pat.charAt(i)!=pat.charAt(j))
nextval[i] = j;
else
nextval[i] = nextval[j]; // 既然相同就继续往前找真前缀
}
else
j = nextval[j];
}
}
/*Return the index of the first occurrence of pat in txt,
or -1 if pat is not part of txt. */
static int KMP(String txt, String pat, int next[])
{
getNext(pat, next);
int i = 0; // the index of txt
int j = 0; // the index of pat
int n = txt.length();//txt length
int m = pat.length();//pat length
while (i < n && j < m)
{
if (j == -1 || txt.charAt(i) == pat.charAt(j)) // P 的第一个字符不匹配或 S[i] == P[j]
{
i++;
j++;
}
else
j = next[j]; // 当前字符匹配失败,进行跳转
}
if (j == m) // 匹配成功
return i - j;
return -1;
}
}
text: bbc abcdab abcdabcdabde
pattern: abcdabd
loc:15
使用优化KMP,程序运行时间:6316ns
更多推荐




所有评论(0)