「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」。
一、应用场景
字符串匹配问题
- 有一个字符串 str = "absabc1bac",和一个字串 match = "abc"
- 现在要判断 str 是否包含 str2,如果存在,就返回第一次出现的位置,如果没有,则返回-1
二、暴力匹配法
如果用暴力匹配的思路,并假设现在str匹配到i位置,字串match匹配到j位置,则有:
- 如果当前字符串匹配成功(即str[i] == match[j]),则i++,j++,继续匹配下一个字符
- 如果匹配失败(即str[i] != match[j]),令i=i-(j-1),j=0。相当于每次匹配失败时,i回溯,j被置为0。
- 用暴力方法解决的话会有大量的回溯,每次移动一位,若是不匹配,移动到下一位接着判断,浪费了大量的时间。
- 暴力匹配算法实现
- 代码
// 暴力匹配 时间复杂度O(N*M) public static int violenceMatch(String s1, String s2) { if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) { return -1; } char[] str1 = s1.toCharArray(); char[] str2 = s2.toCharArray(); int s1Len = str1.length; int s2Len = str2.length; // i索引指向s1 int i = 0; // j索引指向s2 int j = 0; while (i < s1Len && j < s2Len) { if (str1[i] == str2[j]) { i++; j++; } else { i = i - (j - 1); j = 0; } } if (j == s2Len) { return i - j; } else { return -1; } }
三、KMP 算法介绍
- KMP是一个解决模式串在文本串是否出现过,如果出现过,最早出现的问题的经典算法
- Knuth-Morris-Pratt 字符串查找算法,简称为“KMP 算法”,常用于在一个文本串S内查找一个模式串P的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H.Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。
- KMP算法就利用之前判断信息,通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间。
四、KMP 算法最佳应用
字符串匹配问题
有一个字符串str1 = "BBC ABCDAB ABCDABCDABDE",和一个字串str2 = "ABCDABD",现在要判断str1是否包含str2,如果包含,就返回第一次出现的位置,如果不包含,则返回-1。
要求:使用KMP算法完成判断,不能使用简单的暴力匹配算法
- 首先,用str1的第一个字符和str2的第一个字符去比较,不符合,关键词向后移动一位
- 重复第一步,还是不符合,再后移
- 一直重复,直到str1有一个字符与str2的第一个字符符合为止
- 紧接着比较字符串和搜索词的下一个字符,还是符合。
- 遇到str1有一个字符与str2对应的字符不符合
-
这时候,想到的是继续遍历str1的下一个字符,重复第1步。(其实是很不明智的,没有必要再做重复的工作,一个基本事实是,当空格与D不匹配时,其实知道前面六个字符是“ABCDAB”。KMP算法的想法是,设法利用这个已知信息,不要把搜索位置移回到已经比较过的位置,继续把它向后移,这样就提高了效率。)
-
怎么做到把刚刚重复的步骤省略掉?可以对
str2计算出一张《部分匹配表》。 -
介绍《部分匹配表》怎么产生的,先介绍前缀和后缀
根据前缀与后缀的定义,可以退出匹配表数组arr[0] = -1,arr[1] = 0。
arr[i]:当前i位置往前(不包括i)即 0 ~ i-1 范围内前缀与后缀最长公共字串长度。
代码如下:
// 时间复杂度为O(N)
public static int getIndexOf(String s1, String s2) {
if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) {
return -1;
}
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int x = 0;
int y = 0;
int[] next = getNextArray(str2);
while (x < str1.length && y < str2.length) {
if (str1[x] == str2[y]) {
x++;
y++;
} else if (next[y] == -1) {
x++;
} else {
y = next[y];
}
}
return y == str2.length ? x - y : -1;
}
// 匹配表
private static int[] getNextArray(char[] str2) {
if (str2.length == 1) {
return new int[]{-1};
}
int[] next = new int[str2.length];
next[0] = -1;
next[1] = 0;
int i = 2; // 目前在哪个位置上求next数组的值
int cn = 0; // 当前是哪个位置的值再和i-1位置的字符比较
while (i < next.length) {
if (str2[i - 1] == str2[cn]) { // 配成功的时候
next[i++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
}
五、总结
本篇介绍了KMP是什么?KMP能解决什么问题?最重要的是要理解匹配表的代表的含义。