算法第二讲(字符串匹配KMP)
从2019年9月开始,会把《数据结构》经典的算法介绍一遍。加油,89lovelc
问题介绍
- 在一段字符串里面匹配子串是否存在的问题,比如说"89lovelc" 里面有没有lc,89或者el的字符串。
问题分析
- 虽然我们介绍KMP算法,但是我们不妨先使用暴力美学做一下这个问题,正所谓大力出奇迹,我们可以进行遍历要进行查找的字符串,从第一个字符开始和子串第一个字符开始比较,相等就进行比较第二个,以此类推,直到完全匹配成功。不成功的话,将游标移到查找的字符串的第二个,重新开始遍历。直到最后成功。
- 暴力图解
- 但是我们有没有一种办法,从字串入手进行分析,让其跳过一些已知的字母呢?
算法思想
- 如上面的暴力破解的图来看的话,第一张到第二张图的时候,其实我们挨个遍历,我们已经知道了前面的字母,而且可以调到第二个ad的这里开始遍历的。如下图,这样我们就可以不再是单一的遍历了。
- 得到next数组
- 所以通过上面的列子,我们可以对字符串str进行解析,得到一个数据组,**表示的意思是在字符串j的位置之前有k个数达到 str[0]str[1]…str[k-1] = str[j-k]…str[j-2]str[j-1] **( j> k,不然就是子串相等了,没有比较的意思)。
- 这样就可以得到next这个数据进行下一步的计算了
- 通过next数组进行匹配
- 图A 我们使用i进行遍历原始串,使用j进行遍历匹配串,开始进行一一匹配
- 图B 但是匹配到最后的时候,我们发现有d 和 e 不能进行匹配,匹配失败,但是我们不用进行从头开始,在图B中的数字就是我们的next数组,我们取到e所对应的k值,就是2 ,让 j = 2 相当于我们的匹配串整体往右滑
- 图C 就是我们将 j = 2 之后匹配图,这个时候我们可以继续进行 i,j 的匹配
- 如果匹配成功,i - str.length 的index就是开始匹配下标
- 如果匹配失败,return -1
代码实现
package com.lovelc;
/**
* KMP 看毛片?
*/
public class KMP {
/**
* 得到next 数组
*
* @param string
* @return
*/
public static int[] getNext(String string) {
int[] next = new int[string.length()];
//j 进行遍历string
int j = 0;
//k 进行记录匹配个数
int k = -1;
next[0] = -1;
char[] chars = string.toCharArray();
while (j < chars.length - 1) {
if (k == -1 || chars[j] == chars[k]) {
j++;
k++;
next[j] = k;
} else {
// 不能进行匹配 就进行回退到 子串进行匹配
k = next[k];
}
}
return next;
}
/**
* 进行匹配
* @param s 被匹配串
* @param find 匹配串
* @return -1 匹配失败 其他为 匹配开始的index
*/
public static int KMPIndex(String s, String find) {
int[] next = getNext(find);
int i = 0;
int j = 0;
while (i < s.length() && j < find.length()) {
if ( j == -1 || s.charAt(i) == find.charAt(j) ) {
i++;
j++;
} else {
// 不能进行匹配 子串右滑
j = next[j];
}
}
// 匹配成功
if (j == find.length()) {
return i - find.length();
}
return -1;
}
public static void main(String[] args) {
System.out.println(KMPIndex("adcaddaadcade","adcade"));
System.out.println(KMPIndex("djkalsdjkld","ssdd"));
}
}
运行结果
总结
KMP 说起来还是很简单的,运用到代码里面,还是有几个地方需要去理解清楚的,想明白。