KMP 算法
介绍
1、解决模式串在文本串是否出现过,如果出现过,最早出现的位置的经典算法
2、利用之前判断过信息,通过一个 next 数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过 next 数组找到,前面匹配过的位置
3、文本串长度为 n,模式串长度为 m,时间复杂度 O(n + m)
4、很详尽KMP算法
概念
1、前缀:包含首位字符但是不包含末位字符的子串
2、后缀:包含末位字符但是不包含首位字符的子串
3、next[i] = j:即模式串 P 的前i + 1个字符(包括i)组成的字符串中,最长公共前后缀子串的长度为 j
步骤
1、通过自匹配获取模式串 P 的 next[],该 next[] 实际上是前缀表:i 对应值表示,下标 i 之前(包括 i)的字符串中,最长公共前后缀的长度
(1)初始化:i 代表后缀末尾,j 代表前缀末尾,i 初始为1,j 初始为 0,next[0] = 0
(2)前后缀不同: 保证 j > 0 情况下,令 j = next[j - 1];
(3)前后缀相同:i++,j++
(4)更新 next 数组:next[i] = j;
(5)模式串遍历完毕
2、 i 指针初始指向文本串 S 下标 0,j 指针初始指向模式串 P 下标 0,字符匹配时,i++,j++
3、当文本串 S 与模式串 P 下标为 a 的字符不匹配时
(1)根据前一个字符下标 j - 1,获取 next[j - 1] == c,即模式串 P 前 j 个字符(不包括 j)组成的子串中,最长公共前后缀字串长度为 c
(2)模式串 P 的指针 j 应该回退到下标 c,j = c,转步骤 2
4、直到遍历完文本串,返回 -1;或模式串完全匹配,返回 i - j + 1
代码实现
public class KMP {
//KMP算法
public static int kmpMatch(String source, String target) {
int[] next = getNext(target);
for (int i = 0, j = 0; i < source.length(); i++) {
while (j > 0 && source.charAt(i) != target.charAt(j)) {
j = next[j - 1];
}
if (source.charAt(i) == target.charAt(j)) {
j++;
}
if (j == target.length()) {
return i - j + 1;//匹配成功,+1原因:最后字符相同时,有j++,要+1
}
}
return -1;
}
//获取一个target的next数组
public static int[] getNext(String target) {
int[] next = new int[target.length()];//创建next数组
next[0] = 0;//下标0没有前缀、后缀,最长公共前后缀子串的长度为0
for (int i = 1, j = 0; i < target.length(); i++) {
while (j > 0 && target.charAt(i) != target.charAt(j)) {//前后缀不同,令j回溯,使前后缀相同或j为0
j = next[j - 1];//根据j前一个元素值,就是j要回溯到的下标
}
if (target.charAt(i) == target.charAt(j)) {
j++;
}
next[i] = j;
}
return next;
}
}
next[] 优化
1、假设优化前 next[] 为:最长公共前后缀子串长度表,后移一位,next[0] = -1(与上文实现无关),当字符不匹配时,模式串 P 的指针 j 回退到下标 next[j] == c
2、问题:当字符不匹配时,模式串 P 的指针 j 回退到下标 next[j] == c,若模式串 P 下标 j 和下标 c 的元素相同,字符再次匹配时必定失败
3、不能允许 P[j] == P[c],两值相等,递归,即令 next[j] = next[c],直到两值不相等
代码实现
public class KMP {
//KMP算法
public static int kmpMatch(String source, String target) {
int[] next = getNext(target);
int i = 0;//指向文本串
int j = 0;//指向模式串
while (i < source.length() && j < target.length()) {
if (j == -1 || source.charAt(i) == target.charAt(j)) {
i++;
j++;
} else {
j = next[j];
}
}
if (j == target.length()) {
return i - j;
} else {
return -1;
}
}
//获取一个target的next数组
public static int[] getNext(String target) {
int[] next = new int[target.length()];//创建next数组
next[0] = -1;
int i = 0;
int j = -1;
while (i < target.length() - 1) {
if (j == -1 || target.charAt(i) == target.charAt(j)) {
i++;
j++;
if (target.charAt(i) != target.charAt(j)) {
next[i] = j;
} else {
next[i] = next[j];
}
} else {
j = next[j];
}
}
return next;
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战