您的位置:首页 > 编程语言 > Go语言

Algorithm - KMP 字符串匹配算法

2017-02-27 22:41 246 查看

前言

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。

以上是百度百科对 KMP 的描述,在开始介绍 KMP 算法时我们先想一下如何对一字符串找到和它是否匹配的字符串。

BF 算法

暴风(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。时间复杂度为 O (m * n)。

代码实现:

int bfMarch(String text, String pattern) {
int index = -1;
int i = 0;
int j = 0;
int k = 0;
while (i < text.length() && j < pattern.length()) {
if (text.charAt(i) == pattern.charAt(j)) {
i++;
j++;
} else {
j = 0;
i = ++k;
}
}
if (j == pattern.length()) {
index = i - pattern.length();
}
return index;
}


以上代码等同于

int bfMarch(String text, String pattern) {
int index = -1;
for (int i = 0; i < text.length(); i++) {
for (int j = 0; j < pattern.length(); j++) {
if (text.charAt(i) != pattern.charAt(j)) {
break;
}
if (j == pattern.length() {
return i - pattern.length();
}
}
}
return index;
}


KMP 算法

BF 算法比较简单,因为符合我们的逻辑思维,时间复杂度接受不了,在某种意义上为 O (m^2)。接下来我们看看另一种算法,它的时间复杂度为 O (n + m),空间复杂度为 O(m)。刚开始学习这个算法时,在网上找的资料我看的是蒙*的,说的太高深了,直到我在 B 站看了一个大神的视频,资源里有链接。我非常推荐大家先去看视频,比我描述的好多了。

pattern 字符串是和 text 字符串匹配的字符串,pattern.length() <= text.length()。

KMP 的主要思想是创建一容量和 pattern 字符串一样的整型数组,里面存储与前缀相同字符的位置,如:

abcdabaa  // pattern
01234567  // 数组下标
00001211  // 数组存储的值


值如何求?使用两个指针 i、j,arr[0] = 0,i 指向 arr[0],j 指向 arr[1],当判断 pattern.charAt(0) == pattern.charAt(1) 时,arr[1] = i + 1,即 0 + 1,如上列的第 3 和第 4 位,之后 i++ 和 j++。当不匹配时,则向前移一位,i 被赋值为 arr[i - 1],即 i = arr[i - 1],直到 i 变为零,如果还不匹配,则 j++。最后,j > pattern.length() 时结束。

数组的值表示,当 pattern 和 text 对比不匹配时,向前移动一位,pattern 移动的到该值表示的位置。

代码:

/**
* 当字符不匹配时,可根据下标返回对应位置。
* 目的:减少匹配次数
*
* @param patternStr
* @return
*/
private static int[] computeTemporaryArray(String patternStr) {
char[] pattern = patternStr.toCharArray();
int[] lps = new int[pattern.length];
int index = 0;
for (int i = 1; i < pattern.length; ) {
if (pattern[i] == pattern[index]) {
lps[i] = index + 1;
index++;
i++;
} else {
if (index != 0) {
index = lps[index - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}


然后就开始匹配两字符串了,代码如下:

/**
* 找到 pattern 在 text 第一次出现的位置
*
* @param textStr
* @param patternStr
* @return pattern 第一个字符的位置
* -1 表示不匹配
*/
private static int indexOfStr(String textStr, String patternStr) {
int index = -1;
char[] text = textStr.toCharArray();
char[] pattern = patternStr.toCharArray();
// KMP
int lps[] = computeTemporaryArray(patternStr);
int i = 0;
int j = 0;
while (i < text.length && j < pattern.length) {
if (text[i] == pattern[j]) {
i++;
j++;
} else {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
if (j == pattern.length) {
index = i - pattern.length;
}
return index;
}


完整代码

这里还有另一种写法

public class KMP {
public static int KMPSearch(String txt, String pat, int[] next) {
int M = txt.length();
int N = pat.length();
int i = 0;
int j = 0;
while (i < M && j < N) {
if (j == -1 || txt.charAt(i) == pat.charAt(j)) {
i++;
j++;
} else {
j = next[j];
}
}
if (j == N)
return i - j;
else
return -1;
}
public static void getNext(String pat, int[] next) {
int N = pat.length();
next[0] = -1;
int k = -1;
int j = 0;
while (j < N - 1) {
if (k == -1 || pat.charAt(j) == pat.charAt(k)) {
++k;
++j;
next[j] = k;
} else
k = next[k];
}
}
public static void main(String[] args) {
String txt = "BBC ABCDAB CDABABCDABCDABDE";
String pat = "ABCDABD";
int[] next = new int[pat.length()];
getNext(pat, next);
System.out.println(KMPSearch(txt, pat, next));
}
}


资源

【soso字幕】汪都能听懂的KMP字符串匹配算法【双语字幕】
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: