kmp 算法
前言
kmp 是很早之前看的算法,现在又重新看了一下,感觉这次的理解更深了。
kmp 算法是一个非常精妙的算法,他巧妙的避免了重复的遍历值。
原理
KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。
next数组
kmp 的核心是构建next数组,其中next数组记录的是如果字符串匹配失败 我们将从什么地方进行匹配。
下面,我们可以进行查看next 数组的构建
其中,next 数组的长度与被匹配的字符串相等。
位置 | 0 | 1 | 2 | 3 | 4 | 5 |
字符串 | a | b | a | b | b | a |
next | -1 | 0 | 0 | 1 | 2 | 0 |
我们可以看到next[i] 的值指的是我们如果失败,我们将从那个位置继续查找。例如如果第一个位置失败,这个代表我们没有位置可以继续,继续运行 如果是第3个位置失败 代表我们从第一个位置继续查找。
也就是说 next[i] = j,代表 pattern[0,j) = pattern[i-j,i) 这个j是满足公式的最大值。
通俗的讲,就是从0开始的子串,与到i-1的子串,有多少相同的数量。
其中next[0] = -1;
例子:abcabd
next[0] = -1
next[1] = 0
next[2] = 0
next[3] = 0
next[4] = 1
next[5] = 2
既然我们知道next数组的求解方法,那么下面我将用2种方法来进行求解,第一种是比较笨拙的方法,第二种是比较常用的方法。
笨拙求解next 数组
一种比较笨拙的方法是采用三重循环,判断next[i]前面有几个相同,以相同字母的数量最多的为准,
public int[] getNextStupid(String ps){
int[] next = new int[ps.length()];
next[0] = -1;
for(int index = 1;index<ps.length();index++){
int isRight = 0;
for(int j=1;j<index;j++){
boolean flag = true;//判断是否是一样的
for(int k=0;k<j;k++){
if(ps.charAt(k)!=ps.charAt(index+k-j)){
flag = false;
break;
}
}
if(flag){
isRight = j;
}
}
next[index] = isRight;
}
return next;
}
常用的next数组求解办法
其实我们不需要向上面那样,每一次都要求,因为next数已经记录着相同字符串的数量,而后一个相同字符数量是由前一个相同字符数量决定,我们可以采用一个动态规划的方法求得next[i]的值。
public int[] getNext(String ps) {
int[] next = new int[ps.length()];
int index = 1;// 读取当前的字符串的位置
int pre = -1;// 代表 位置 ps[0 至 pre] 与 ps[index - pre 至 index-1] 是相同的 pre<index
next[0] = -1;// 0 位置前面是没有任何相同
for(;index<ps.length();index++){
//如果前面没有值或者str[index-1]的值和str[pre]的值相同,那么我们直接查找的位置+1
if(pre==-1||ps.charAt(index-1) == ps.charAt(pre)){
pre++;
next[index] = pre;
continue;
}
// 如果匹配失败,我们从next数组中找到下一个匹配的位置
pre = next[pre];
}
return next;
}
查找
既然我们已经构建next成功,我们就可以直接使用了
public int index(String str,String pattern){
int[] next = getNext(pattern);
int i = 0;
int j = 0;
while (i<str.length()&&j<pattern.length()){
if(str.charAt(i)==pattern.charAt(j)){
i++;
j++;
}else {
if(j==0){
i++; // 当str[i]与pattern[0]个不同时 我们需要匹配下一个字符
}else {
j = next[j];
}
}
}
if(j!=pattern.length()){
return -1; // 匹配失败
}
return i-pattern.length();
}
完整代码
package com.company;
import javax.security.sasl.SaslServer;
import java.util.Arrays;
public class KMP {
public int[] getNext(String ps) {
int[] next = new int[ps.length()];
int index = 1;// 读取当前的字符串的位置
int pre = -1;// 代表 位置 ps[0 至 pre] 与 ps[index - pre 至 index] 是相同的 pre<index
next[0] = -1;// 0 位置前面是没有任何相同
for(;index<ps.length();index++){
//如果前面没有值或者str[index]的值和str[pre]的值相同,那么我们可以直接从那个位置开始查找
if(pre==-1||ps.charAt(index-1) == ps.charAt(pre)){
pre++;
next[index] = pre;
continue;
}
// 如果匹配失败,我们从next数组,前一个相同的位置在进行匹配
pre = next[pre];
}
return next;
}
public int[] getNextStupid(String ps){
int[] next = new int[ps.length()];
next[0] = -1;
for(int index = 1;index<ps.length();index++){
int isRight = 0;
for(int j=1;j<index;j++){ // 0个相同我们不需要看,所以我们从1开始出发,其最多也只能是index-1个相同
boolean flag = true;//判断是否是一样的
for(int k=0;k<j;k++){
if(ps.charAt(k)!=ps.charAt(index+k-j)){
flag = false;
break;
}
}
if(flag){
isRight = j;
}
}
next[index] = isRight;
}
return next;
}
public int index(String str,String pattern){
int[] next = getNext(pattern);
int i = 0;
int j = 0;
while (i<str.length()&&j<pattern.length()){
if(str.charAt(i)==pattern.charAt(j)){
i++;
j++;
}else {
if(j==0){
i++; // 当str[i]与pattern[0]个不同时 我们需要匹配下一个字符
}else {
j = next[j];
}
}
}
if(j!=pattern.length()){
return -1; // 匹配失败
}
return i-pattern.length();
}
}
// KMP Test
class KmpTest{
public static void main(String[] args) {
KMP kmp = new KMP();
String s1 = "aaaaaaaaabcdeeeeaassd";
String s2 = "bcde";
System.out.println(kmp.index(s1,s2));
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具