字符串KMP算法
作者:互联网
一、算法介绍:
KMP算法主要用于字符串包含问题,如:
给定字符串A,B,判断B是否是A的子串
在这里,我们把等待匹配的字符串A称为母串,用来匹配的串B称为模式串
二、算法流程:
(感性理解???)
如果按照一般思路,我们循环A串的各个元素,判断是否包含B串,算法时间复杂度过高,因为这个算法忽略了之前我们已经对A,B串进行了比较的事实,所以在运行时进行了许多重复的判断,导致了时间复杂度过高。
因此,我们可以考虑对其进行优化~~
不要喷,我的图是搜的,我真心不会画图qwq
首先,来看一个例子。我们设A串(上面的串)的数组下标为A【1~n】,B串(下面的串)的数组下标B【1~m】
经过比较我们可以发现,当 i=4, j=4 时,字符串中的元素不一样了,也就是说我们现在找到的子串不合法了,所以为了节省时间,我们要跳过尽可能多的元素(因为经过比较我们发现A串中1~3个元素都与B串中第一个元素不一样了,所以不需要再浪费时间进行比较了),但是在跳过元素时也应当满足当有重复元素时,保留重复元素的特性,所以我们应该让B[1]与A[5]冲齐。
我们再看另一个例子
原来的字符串是这样的:
A B C A B C D H I J K
A B C E
我们发现当元素A E不一样了,但是B串中的第一个元素A又可以和A串中的第四个元素A重合,所以我们就把字符串B移动到当前位置
再来一个例子!
我们发现此时元素C D 不一样,但是A串中的第三个元素A与B串中的第一个元素A是匹配的啊,所以,我们开始移动B串:
下一个例子也是一样滴:
这个例子不进行过多解释,还请读者自行思考(但是我相信 聪明的你们一定都会!!)
(好吧这个例子好像挺重要的所以我还是讲一讲把TAT)
匹配之后我们发现元素C B不一样,但是我们不可以直接把B中的第一个元素A挪到第i+1个位置,因为那样做的话我们会失去许多重复元素(在这个题里是A[4~5]和B[1~2],都是A B)。
所以正确的做法是当存在重复元素时,让模式串与母串重合
从上面几个例子中我们可以找到一些规律:
当匹配失败后,j要移动的下一个位置K(在母串中的对应位置K哈,不是模式串),有着这样的关系:
最前面的K个字符和J之前的K个字符是一样的!!!
用数学公式表达就是:
P[0 ~ k-1] == P[j-k ~ j-1]
这样的话,如果 我们首先设置 j 要移动下一个位置为p[j],那么我们就可以在母串中寻找模式串,代码如下:
j=0; for(int i=1;i<=n;i++){ while(j>0&&a[i]!=b[j+1]) j=p[j];//如果不相等,那么我们就回到重合部分的 //最后一个元素,从最后一个元素开始寻找 if(a[i]==b[j+1]) j++; if(j==0){ cout<<i-m+1<<endl;//表示输出当前相同字符串的首字母下标 j=p[j];//因为可能存在多个包含关系,所以我们继续搜索 } }
这样,我们就完成了在母串中对于模式串的查找。
But but,我们是不是忘记了什么重要的东西???
我们还没有说明怎么找到数组P啊!!!
重头戏来了(手动滑稽)
我们已经知道,P数组是用来寻找A B字符串重合元素的最后一个位置的数组变量(或许也可以称之为指针??) 那么其实....我们可以直接把P数组定义为:求模式串中的子串的真重复前缀和后缀(强烈建议停下来好好理解一下这句话)
我们来看一个例子:
我们通过A B串的对比可以发现,其实在不匹配元素之前,我们找到的A B子串是等价的,所以我们完全可以把找A B 串的重合部分(其实不太准确)转化为求B 串的前缀后缀(可爱)
我相信聪明的您们都已经明白了
所以,我们就相当于在一个字符串中再次运用KMP算法(其实整个KMP算法就是一个嵌套问题)
我自己造一个例子吧 唉~~
1 2 3 4 5 6 7 8 9
B a b c a b d e a c
P 0 0 0 1 2 0 0 0 ?
就这个吧
P数组代表的是我们可以返回的元素的下标,(也可以理解为如果下一个元素不匹配,我们可以从当前元素找到一条“退路”),所以我们从头开始推P数组
因为B[1]=a,因为它是最左边的元素,所以不可能再有退路,所以它的返回值就是0(相当于返回到数组B[0])
因为B[2]=b,与B[1]=a不相等,所以如果我们发现元素不相等,我们应当再次返回B[0];
B[3]=c同理?
终于到了B[4]=a!!!(精神一震!!) 我们突然发现B[4]=a与B[1]=a相等,所以我们可以从B[4]跳到B[1];
B[5]=B[4+1]正好与B[2]=B[2+1]相等,也就是当前子串的前缀和后缀可以匹配,所以我们直接从B[5]返回B[2];(还记得我们的P数组返回的是重合元素的末尾地址吗?)
其它同理。各位大大可以自己多推几遍(掩盖住我其实并不想多写的事实)
好了!大体思路就是这样,代码实现如下
int j=0; for(int i=2;i<=m;i++)//因为我们的i表示当前元素是否与j+1匹配
//所以我们从i=2开始循环 { while(j>0&&b[i]!=b[j+1]) j=p[j]; if(b[j+1]===b[i]) j++; p[i]=j; }//预处理模式串中所有的情况
总的来说,KMP和上面的代码其实还是很好理解的??(哼,才不告诉你我学了6h+呢)
其实各位大大们只要多看几遍题解+自己推几遍就可以理解了~~~~吧?
完整代码如下:
题目:洛谷P3375 【模板】KMP字符串匹配 https://www.luogu.com.cn/problem/P3375
#include<iostream> #include<cstdio> #include<bits/stdc++.h> using namespace std; int p[1020000],n,m,j; char a[1020000],b[1020000]; int main() { cin>>a+1; cin>>b+1; n=strlen(a+1); m=strlen(b+1); // int j=0; for(int i=2; i<=m; i++) { while(j&&b[i]!=b[j+1]) j=p[j]; if(b[j+1]==b[i]) j++; p[i]=j; }//预处理 j=0; for(int i=1; i<=n; i++) { while(j>0&&a[i]!=b[j+1]) j=p[j]; if(a[i]==b[j+1]) j++; if(j==m) { // cout<<" *"; cout<<i-m+1<<endl; j=p[j];//再次跳回原来的地方,继续判断 } } for(int i=1; i<=m; i++) cout<<p[i]<<" "; return 0; }
——END——
标签:int,元素,算法,数组,KMP,字符串,串中,我们 来源: https://www.cnblogs.com/yxr001002/p/13368009.html