KMP算法通常用于解决模式串匹配问题
一个讲解很好的视频: KMP字符串匹配
前缀:字符串从左开始的任意子串(或者说是字符串的任意首部)
真前缀(又称前缀真子串):是指不包含本身的前缀。
后缀定义:字符串从右开始的任意子串(或者说是字符串的任意尾部)
真后缀(又称后缀真子串):是指不包含本身的后缀。
以x=“ABCD”为例:
前缀:空串,“A”,“AB”,“ABC”,“ABCD”;
后缀:空串,“D”,“DC”、“DCB”,“DCBA”;
类比中学阶段集合概念,真子集就是真前缀,空集就是空字符,子集就是所有的前缀,后缀同理。
二 . KMP算法原理
(1)暴力:
将子串(较短的字符串)的第一位与长串进行一位一位的比较,若出现一位不匹配,整个子串向后移动一位。
初始状态:(i,j作为指针,标记搜索的位置)

找到未匹配的状态:

子串向后移动一位:

暴力搜索会出现子串与长串完全不匹配的情况,会浪费大量的时间。
2.KMP算法的搜索移动
字符组1:
初始状态:

第一次不匹配移动后的状态:

字符组2:
初始状态:

第一次不匹配的移动后的状态:

3.求前缀和后缀的长度

以子串ABABC为例:
| 子串长度 | 子串 | 前后缀长度(小于子串长度) |
| 1 | A | 0 |
| 2 | AB | 0 |
| 3 | A B A | 1 |
| 4 | AB AB | 2 |
| 5 | ABABC | 0 |
表格中前后缀长度就是next数组。
注意:最长相同前后缀不能是字符串本身
给出两个字符串
和
,若
的区间 [l, r] 子串与
完全相同,则称
在
中出现了,其出现位置为 l。
现在请你求出
在
中所有出现的位置。
定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
对于
,你还需要求出对于其每个前缀 s' 的最长 border t' 的长度。
第一行为一个字符串,即为
。
第二行为一个字符串,即为
。
首先输出若干行,每行一个整数,按从小到大的顺序输出
在
中出现的位置。
最后一行输出
个整数,第 i 个整数表示
的长度为 i 的前缀的最长 border 长度。
输入 #1
ABABABC ABA
输出 #1
1 3 0 0 1

对于
长度为 3 的前缀 ABA,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1。
本题采用多测试点捆绑测试,共有 3 个子任务。
。
,
。对于全部的测试点,保证
,
,
中均只含大写英文字母。
- #include
- #include
- #include
- #include
- using namespace std;
- const int maxn=1e5+5;
- string a,b;
- int len1,len2,Next[maxn],next2[maxn];
- void table(string b) //求前缀表
- {
- next2[0]=0; //定义一个数组:用于存储截止至目前i字符串前缀的长度
- int i=1; //从字符串第二个字符开始计算
- int len=0; //用于统计前缀的长度
- while(i
length()) - {
- if(b[i]==b[len]) //如果第i项的字母与len位的字母相同
- {
- len++; //前缀的长度加1;
- next2[i]=len; //当指针搜到i位置的时候,前缀的长度
- i++; //指针后移,进行下一次查找
- }
- else//如果第i项的字母与len位的字母不相同
- {
- if(len>0) //如果此时的前缀长度不为0(小心下标越界)
- {
- len=next2[len-1];
- //前缀长度等于数组存的第len-1的位置的长度(遗留问题)
- }
- else
- {
- next2[i]=len; //数组中存入0
- ++i;//将指针i向后移动1位
- }
- }
- }
- }
-
- void kmp(string a,string b) //KMP算法的核心
- {
- int i=0,j=0; //定义两个指针
- Next[0]={-1}; //把用于存前缀表的数组第一位改成-1
- len1=a.length(); //记录长串的长度
- len2=b.length(); //记录短串的长度
- for(int i=1;i<=len2-1;++i)
- {
- Next[i]=next2[i-1]; //将前缀表的位置向后移动1位
- }
-
- while(i<=len1-1) //搜到长串的最后一个字母前
- {
- if((j==len2-1)&&(a[i]==b[j])) //如果子串全部与长串匹配
- {
- cout<
1<//输出子串s2在长串s1的位置 - j=Next[j];
- //*是难点也是关键点!
- //如果a[i]和b[j]中字符不同,则视为匹配失败,将j的值退回至Next[j]的位置上,直至两者相同
- //好处:减少逐步向后移进行不必要的比较
- }
- if ((a[i]==b[j])||(j==-1))
- //如果未到子串的最后一位相等,或子串第一位序号j对应的是前缀表的-1
- {
- i++;
- j++;
- //两个指针都向后移
- }
- else j=Next[j];
- }
- }
-
- int main()
- {
- cin>>a>>b;
- table(b); //前缀表只需处理子串
- kmp(a,b);
- for(int i=0;i<=len2-1;++i)
- {
- cout<
" "; //输出前缀表 - }
- return 0;
- }
KMP算法的优化:(待填坑)
有些时候,KMP算法还不够快,比如以下的情况: