14.4KMP算法
字符串匹配问题:
1、 有一个字符串 str1= “我爱中国中国爱我爱中国”,和一个子串 str2=“中国爱我”;
2、 现在要判断 str1 是否含有 str2, 如果存在,就返回第一次出现的位置, 如果没有,则返回-1
对于这类寻找父串中是否存在指定子串的问题,有两种解决方法
1、 暴力匹配算法
2、 KMP算法
暴力匹配算法:就是一个字一个字地进行匹配,直到匹配成功或者父串结束为止
1、 如果当前字符匹配成功(即 str1[i] == str2[j]),则 i++,j++,继续匹配下一个字符
2、 如果失配(即 str1[i]! = str2[j]),令 i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为 0
3、 用暴力方法解决的话就会有大量的回溯,每次只移动一位,若是不匹配,移动到下一位接着判断,浪费了大量的时间。(不可行!)
(但是还是要实现一下,万一我就只会这个呢?)
package com.atguigu17.kmp;
/**
* @author peng
* @date 2021/12/5 - 11:27
* <p>
* 查找字符串之暴力匹配算法
*/
public class ViolenceMatch {
public static void main(String[] args) {
String str1 = "我爱中国 中国爱我";
String str2 = "爱我";
int index = violenceMatch(str1, str2);
System.out.println("找到了需要匹配的字符串在:" + index);
str2 = "爱我吗?";
index = violenceMatch(str1, str2);
System.out.println("找到了需要匹配的字符串在:" + index);
}
/**
* 实现暴力匹配算法
*/
public static int violenceMatch(String str1, String str2) {
//将字符串转换成字符数组
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
//获取两个字符数组的长度
int s1len = s1.length;
int s2len = s2.length;
//定义两个指针
int i = 0;//指向字符数组1
int j = 0;//指向字符数组2
//进行字符串的匹配
while (i < s1len && j < s2len) {
//首先保证指针不能越界
if (s1[i] == s2[j]) {
//如果匹配成功,就进行下一个字符的匹配
i++;
j++;
}else {
//如果匹配失败了,i就返回到第一个匹配成功的字符的下一个字符
i = i - j + 1;
//j就直接再次从0开始匹配
j = 0;
}
}
//如果j走到了s2的最后一个位置,说明已经全部匹配成功了
if (j == s2len) {
return i - j;//返回第一个匹配成功时的字符的下标
}else {
//如果没有找到就返回-1
return -1;
}
}
}
KMP算法介绍:
1、 KMP 是一个解决模式串在文本串是否出现过,如果出现过,最早出现的位置的经典算法
2、 Knuth-Morris-Pratt 字符串查找算法,简称为“KMP 算法”,常用于在一个文本串 S 内查找一个模式串 P 的出现位置,这个算法由 Donald Knuth、Vaughan Pratt、James H. Morris 三人于 1977 年联合发表,故取这 3 人的姓氏命名此算法
3、 KMP 方法算法就利用之前判断过信息,通过一个next 数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next 数组找到,前面匹配过的位置,省去了大量的计算时间
4、 如果实在不懂,建议看这篇文章
https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html
KMP算法实践字符串匹配问题:
1、 有一个字符串 str1= “BBC ABCDAB ABCDABCDABDE”,和一个子串 str2="ABCDABD”
2、 现在要判断 str1 是否含有 str2, 如果存在,就返回第一次出现的位置, 如果没有,则返回-1
思路分析图解:
package com.atguigu17.kmp;
/**
* @author peng
* @date 2021/12/5 - 15:04
* <p>
* 利用KMP算法实现字符串的查找
*/
public class KmpAlgorithm {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int[] next = kmpNext(str2);
int index = KmpSerach(str1, str2, next);
System.out.println("第一次出现的位置是:" + index);
}
//Kmp搜索算法
public static int KmpSerach(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()) {
//如果j已经走到了str2的最后一个位置,说明全部匹配
return i - j + 1;//返回第一个匹配的位置
}
}
return -1;//如果没有找到就返回-1
}
//获取一个子串的部分匹配值
public static int[] kmpNext(String dest) {
//创建一个next数组,用于保存部分匹配值,长度就是dest的长度
int[] next = new int[dest.length()];
//很显然,dest的第一个字符的部分匹配值就是0,因为它既没有前缀又没有后缀
next[0] = 0;
for (int i = 1, j = 0; i < dest.length(); i++) {
while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
//当不匹配的时候,j应该回溯,直到重新匹配为止,但不是很懂
j = next[j - 1];
}
/**
* 这个for循环的意思要解释一下:
* i为什么是从1开始?因为上面已经说了,dest的第一个字符的部分匹配值一定是0,已经赋好值了,因此直接从第二个字符开始
* j代表什么意思?首先要搞清楚部分匹配值是怎么来的,部分匹配值就是一个字符的前缀和后缀有多少个公共子串就有多少个部分匹配值
* i表示后缀的开始位置,j表示前缀的开始位置
*/
if (dest.charAt(i) == dest.charAt(j)) {
//如果一个字符串的前缀和后缀包含相同的子串,部分匹配值加一
//注意:这是一个逐渐增加的过程,使用的情况是,A、AA、AAA、AAAA这种部分匹配值逐渐增加的情况
j++;
}
next[i] = j;//将部分匹配值放在对应的位置上
}
return next;//返回部分匹配值的数组
}
}