kmp算法
kmp算法要解决的问题
kmp算法解决的就是匹配字符串,能减少匹配字符串的匹配次数,能够记忆已经匹配过的部分
前缀表
kmp算法中最重要的就是求模式串的前缀表!!!
首先什么是前缀表呢?
前缀表就是由每项的最长相等前后缀组成。
那么什么是最长相等前后缀呢?
要理解最长相等前后缀,首先要理解前缀和后缀。
前缀就是:包括头一项,不包含尾项的字串
后缀就是:不包括头一项,包括尾项的字串
例如模式串 aabaaf 前缀有:a、aa、aab、aaba、aabaa 后缀:f 、af、aaf、baaf、abaaf
那么最长想的前后缀就是,前后缀相等的最长的长度。
那么对于模式串aabaaf中每项的最长相等前后缀
模式串的每项 | 最长相等前后缀 |
---|---|
a | 0 |
aa | 1(前缀a,后缀a) |
aab | 0 |
aaba | 1(前缀a,后缀a) |
aabaa | 2(前缀aa,后缀aa) |
aabaaf | 0 |
模式串aabaaf的前缀表为 0 1 0 1 2 0
看一道例题(使用kmp解决匹配字符串问题):
有主串:aabaabaaf , 模式串aabaaf,看主串中是否包含模式串
暴力解法,双循环,时间复杂度O(N*M) 模式串和主串从头开始比较,碰到不匹配的就向后移动一位,每次都从模式串的头开始进行匹配 直到主串查找结束后停止。
如果使用kmp算法就不一样,当发生不匹配的时候,不会从头开始匹配,aabaab和aabaaf,中b和f发生不匹配就会利用【前缀表】,看发生冲突的前一项的前缀表的值是2,则跳到字串下标为2(索引从0开始)的地方,也就是b,然后从b开始后与主串继续匹配。用模式串的部分字串baaf和主串发生冲突后面baaf的进行匹配。匹配成功说明模式串和主串匹配成功
伪代码实现
求前缀表的伪代码
1、初始化
2、当前缀和后缀不发生相等的情况
3、前缀和后缀发生相等的情况
4、为前缀表赋值
对模式串进行循环
for(int i = 1;i<s.length();i++){
//循环体的内容为一下四步
}
1、初始化
设j为前缀的末尾,i为后缀的末尾 j = 0 ; i的初始化在循环中
设next数组为前缀表 next[0] = 0(因为前缀表的头节点一定没有最长相等前后缀)
2、当前缀和后缀不发生相等的情况 也就是模式串 j 指向的字符与模式串 i 指向的字符不相等(使用java伪代码标识为 s.charAt(i)!=s.charAt(j))
前后缀不相等,我就需要回退
while(j>0&&needle.charAt(i)!=needle.charAt(j)){
//例如模式串aabaa中a!=b 注意这里要一直回退,直到找到相等的前后缀 所以使用while
j = next[j-1];
//回退到前一个的数组的最大相等前后缀的位置,这个遵顼不变量原则,就是我再匹配主串和模板串的时候发生不匹配就是看的前一个的前缀表
}
3、前缀和后缀发生相等的情况
当前后缀发生相等,则说明找到最大相等前后缀,然后j向后移动
if(needle.charAt(i) == needle.charAt(j)){
//前后缀相等 j++;
j++;
}
4、将 j 的值赋值给前缀表
next[i] = j;//赋值next
力扣原题
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1: 输入: haystack = “hello”, needle = “ll” 输出: 2
示例 2: 输入: haystack = “aaaaa”, needle = “bba” 输出: -1
说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符
class Solution {
public int strStr(String haystack, String needle) {
//前缀表,不减1,也不向右移动
if(needle.length() == 0){
return 0;
}
//创建next数据,也就是前缀表,这个里面存的就是模式串对应下表字串的最长相等前后缀的长度
int[] next = new int[needle.length()];
//获得next数组
getNext(next,needle);
//完成匹配
int j = 0;//再next数组中起始为0
for(int i = 0;i<haystack.length();i++){
while(j>0&&haystack.charAt(i) != needle.charAt(j)){
j = next[j-1];
}
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
if(j == needle.length()){
return i-j+1;
}
}
return -1;
}
//获得next数组
public void getNext(int[] next,String needle){
//1、初始化
int j = 0;//指向前缀的末尾,同时也指向i以前(包括i)的最长相等前后缀的长度(也就是j指向的next下表索引)
next[0] = 0;//初始化第一个next数组的值为0,因为他必定为0
for(int i = 1;i<needle.length();i++){//初始化后缀末尾为1
//2、前后缀不相等的情况,(也就是i!=j ) 不相等,j就要回退
while(j>0&&needle.charAt(i)!=needle.charAt(j)){//例如模式串aabaa中a!=b 注意这里要一直回退,知道找到相等的 所以使用while
j = next[j-1];//回退到前一个的数组的最大相等前后缀的位置,这个遵顼不变量原则,就是我再匹配主串和模板串的时候发生不匹配就是看的前一个的前缀表
}
if(needle.charAt(i) == needle.charAt(j)){//前后缀相等 j++;
j++;
}
next[i] = j;//赋值next
}
}
}
*此文章是看代码随想录的总结,值得推荐代码随想录
地址:https://www.programmercarl.com/