第3章 KMP算法
3.1 字符串匹配
3.2暴力匹配的算法
假设现在str1匹配到i位置,子串str2匹配到j位置,则
1)如果当前字符匹配成功(str1[i] == str2[j] ),则i++,j++,继续匹配下一个字符
2)如果匹配失败(str1[i] != str2[j] ) ,令i = i - ( j-1 ),j = 0;相当于每次匹配失败时,i回溯,j 被置为0
3)用暴力方法解决的话会有大量的回溯,每次只移动一位,若是不匹配,移动到下一位接着判断,浪费大量的实践
代码实现
package com.ldm.kmp;
/**
* @author 梁东明
* 2022/9/12
* 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
* 点击setting在Editor 的File and Code Templates 修改
*
* 字符串匹配
*/
public class ViolenceMath {
public static void main(String[] args) {
String str1 = " ldm mdl nomLn dnh";
String str2 = "nh";
int i = violenceMath(str1, str2);
if ( i == -1){
System.out.println("str1中没有str2");
}else {
System.out.println("str2在str1出现的索引位置在:" + i);
}
}
//暴力匹配算法实现
public static int violenceMath(String str1, String str2){
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
int s1Len = s1.length;
int s2Len = s2.length;
int i = 0; //字符串s1的初始索引
int j = 0; //字符串s2的初始索引
//
while ( i < s1Len && j < s2Len){
if ( s1[i] == s2[j]){//当出现第一个字符相等,继续比较
i++;
j++;
}else {
//不相等的话,i 后退 j-1 步
//j 回到 第一步 即置为0
i = i - (j-1);
j = 0;
}
}
//当退出循环,表示已经把两组字符串遍历完成了
//接下就要看一看是否出现字符匹配的现象了
if ( j == s2Len){ //如果str1中有str2的话,那么str2 应该遍历到尾部
return i - j; //那么就把str2在str1出现的索引返回
}else {
return -1;
}
}
}
3.3 KMP算法基本介绍
1)KMP是一个解决模式串在文本串是否出现过,如果出现过,最早出现大的位置的经典算法
2)Knuth-Morris_Pratt字符串查找算法,简称:KMP算法,常用于在一个文本串内查找一个模式串P的出现位置,这个算法是由DonaldKnuth、vaughanPratt、JamesH.Morris三人于1977年联合发表,故取这三人的姓氏命名此算法
3)KMP方法算法就利用之前判断过信息,通过一个next数组,保存模串中前后最长公共子序列的长度,每次回溯时,就通过next数组找到,前面匹配过的位置,省去了大量的计算时间
ABCDA 因为头尾都是A,所以是1,ABCDAB,因为头尾都是AB,所以是2
移动位数 = 已匹配的字符数 - 对应的部分匹配值
KMP
1、先得到子串的部分匹配表
2、使用部分匹配表完成KMP匹配
这里ABCDABD,当匹配上ABCDAB的时候,我们想让他跳过中间没用的部分,直接到第二个AB处
而对于ABCDAB这个字符串,因为前缀AB和后缀AB是一样的,我们就可以把第二个AB当作新的起点
而对于ABCDAB这个字符串,因为前缀AB和后缀AB是一样的,我们就可以把第二个AB当作新的起点
我们就可以把第二个(也就是那个后缀)当作新的起点,而这里当然是要用这个后缀的第一个字符
而想达到这个字符的位置,我们需要前进的步数恰巧就是(已匹配字符的总长度 - 后缀的长度)
也就是从前缀的第一个字母到后缀的第一个字母要前进几步
代码实现
获取一个字符串的(子串)的部分匹配值表
//获取一个字符串的(子串)的部分匹配值表
public static int[] kmpNext(String dest){
//创建一个next数组保存部分匹配值
int[] next = new int[dest.length()];
for (int i = 1,j =0; i < dest.length(); i++) {
//当dest.charAt(i) != dest.charAt(j)满足时,我们需要从next[j-1]获取j
//直到我们发现dest.charAt(i) == dest.charAt(j)满足时,才退出
//这时kmp算法的核心!!!!!!
while( j > 0 && dest.charAt(i) != dest.charAt(j)){
j = next[j-1];
}
//当dest.charAt(i) == dest.charAt(j)满足时,部分匹配值就+1
if ( dest.charAt(i) == dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
有注解的完整代码
package com.ldm.kmp;
import java.util.Arrays;
/**
* @author 梁东明
* 2022/9/12
* 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
* 点击setting在Editor 的File and Code Templates 修改
*
* kmp算法
*/
public class KMPAlgorithm {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int[] next = kmpNext(str2); //求str2的部分匹配值表
System.out.println(Arrays.toString(next));
int i = kmpSearch(str1, str2, next);
if ( i == -1){
System.out.println("str1字符串没有包含str2");
}else {
System.out.println("str2在str1出现的索引位置在:" + i);
}
}
/**
* 写出kmp搜索算法
*
* @param str1 str1
* @param str2 str2
* @param next str2的部分匹配值表
* @return int str2如果存在str1,就返回其在str1出现的索引,没有就返回-1
*/
public static int kmpSearch(String str1, String str2, int[] next){
for (int i = 1,j =0; i < str1.length(); i++) {
//当dest.charAt(i) != dest.charAt(j) 时,我们需要从next[j-1]获取j
//直到我们发现dest.charAt(i) == dest.charAt(j)满足时,才退出
//这时kmp算法的核心!!!!!!
while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
j = next[j - 1];
}
//当dest.charAt(i) == dest.charAt(j)满足时,部分匹配值就+1
if (str1.charAt(i) == str2.charAt(j)) {
j++;
}
if ( j == str2.length()){
return i - j + 1;
}
}
return -1;
}
//获取一个字符串的(子串)的部分匹配值表
public static int[] kmpNext(String dest){
//创建一个next数组保存部分匹配值
int[] next = new int[dest.length()];
for (int i = 1,j =0; i < dest.length(); i++) {
//当dest.charAt(i) != dest.charAt(j)满足时,我们需要从next[j-1]获取j
//直到我们发现dest.charAt(i) == dest.charAt(j)满足时,才退出
//这时kmp算法的核心!!!!!!
while( j > 0 && dest.charAt(i) != dest.charAt(j)){
j = next[j-1];
}
//当dest.charAt(i) == dest.charAt(j)满足时,部分匹配值就+1
if ( dest.charAt(i) == dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
}
无注解的完整代码
有注解和无注解的代码的区别是:
有注解的代码是我理解老师代码后额外添加自己的注解
无注解的代码是我凭借对kmp算法的了解,自己手敲出来的,
所以一个注解都没有,放心食用。
package com.ldm.kmp;
import java.util.Arrays;
/**
* @author 梁东明
* 2022/9/12
* 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
* 点击setting在Editor 的File and Code Templates 修改
*/
public class MyKMPAlgorithm {
public static void main(String[] args) {
String str1 = "caudicgbahvbajkccb";
String str2 = "cb";
int[] next = kmpNext(str2);
System.out.println("部分匹配表 = " + Arrays.toString(next));
int i = kmpAlgorithm(str1, str2, next);
if ( i == -1){
System.out.println("str1没有包含str2");
}else {
System.out.println("str2在str1出现的索引是 = " + i);
}
}
public static int kmpAlgorithm(String str1,String str2, int[] next){
for (int i = 0 ,j =0; i < str1.length(); i++) {
while ( j > 0 && str1.charAt(i) != str2.charAt(j)){
j = next[j-1];
}
if (str1.charAt(i) == str2.charAt(j)){
j++;
}
if ( j == str2.length()){
return i - j + 1;
}
}
return -1;
}
public static int[] kmpNext(String dest){
int[] next = new int[dest.length()];
for (int i = 1, j= 0; i < dest.length(); i++) {
while (j > 0 && dest.charAt(i) != dest.charAt(j)){
j = next[j-1];
}
if (dest.charAt(i) == dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
}
本次KMP算法 的教程出自韩顺平的数据结构与算法
数据结构和算法教程,哔哩哔哩详细教程
在 160-163p.
最后,认识一下,我是小白。努力成为一名合格的程序员。期待与你的下次相遇。