深度解读KMP算法

/*
深度解读KMP算法:
    一个人能走的多远不在于他在顺境时能走的多快,而在于他在逆境时多久能找到曾经的自己。 -KMP
    
    KMP算法常用于字符串间的匹配问题
    
    给定主串S和模式串P,S的长度为n,P的长度为m,在主串P中寻找模式串S出现的位置
    (为方便理解S和P的下标均从1开始)
    
    在解读KMP算法之前让我们先了解暴力枚举的做法:
    
    依次枚举主串S中下标为1,2,3,...n-m+1的位置,
    将其作为比较的起点依次与模式串P进行匹配,
    若匹配成功则记录位置,匹配失败则换下一位进行匹配
    
    在上述匹配过程中,每一次失败的匹配都没有为我们提供丝毫的经验,
    我们还是如同第一次匹配一样去麻木的从下一位开始匹配,这显然是不太聪明的做法,
    所以穷举的时间复杂度是O(mn)的。
    
    接下来进入KMP的正题:
    
    KMP其实就是一个不断提取失败经验的过程,每一次不成功的匹配都为它的下一次匹配积累了经验
    
    接下来考虑这样一种情况:
                                  1 2 3 4 5 6 7 8 9 10 11 12
                        主串S     c a l g o a i t m n  t  g
                        
                      模式串P       a l g o a e
                                    1 2 3 4 5 6
            
        用s[i]和p[j]来表示正在匹配的字符,则我们会发现模式串的前5个字符都匹配成功了,
        只有最后一个字符没有匹配成功,此时,我们的 i = 7,j = 6
        
        接下来就是KMP的精髓:跳过不可能成功的尝试
        
        在上述失败的匹配过程中我们获得了如下信息:
        在主串的i = 7的位置之前的5个字符和模式串P的前5个字符是正确匹配的
        假设我们手动模拟的移动模式串P,我们会发现移动的前4步都是不可能成功的,
        唯有当模式串的某一段前缀(下列中为模式串中j=1处的a)和i=7之前的某一段后缀(下列中为主串中i=6处的a)匹配时,我们才有可能匹配成功
                        
                        主串S      1 2 3 4 5 6 7 8 9 10 11 12
                                   c a l g o a i t m n  t  g
                                             
                      模式串P        a l g o a  e
                                     1 2 3 4 5  6
        
        而因为模式串P的前5位都正确匹配了,
        所以i = 7的某一段后缀同样是包含于模式串P中的
        
        所以容易得知这里的前缀和后缀都是包含于模式串P中的,
        而基于贪心的匹配原则,我们自然希望模式串P的前缀和后缀匹配的越多越好
        
        由此我们引入模式串P的一个关键因素next数组
        next[i]存储的是在模式串P的前i个字符中,前缀和后缀的最大匹配程度
        (其中前缀和后缀不能相等,因为模式串P当然最大匹配自己,但这没有对解决问题没有任何意义)
        
        下面我们举一个形象的例子:
            假设模式串P是    a b c a b d
            则我们可以得到
                           next[1] = 0;
                           next[2] = 0;
                           next[3] = 0;
                           next[4] = 1;
                           next[5] = 2;
                           next[6] = 0;
        
        相信你已经熟悉了next数组的含义,接下来让我们模拟一下KMP匹配字符串的过程
        假设在主串S中模式串的前j1位均已经匹配,但其j1+1位和S串中的第i位并不匹配,
        这时我们并不急于跳到下一个i的位置,而是尝试寻找一个新的j2的位置,
        使得此时已经匹配好了若干元素,而不是象暴力枚举一般每次从零开始,
        我们并不是从0开始的,按照next数组的定义,
        我们是在已经匹配好了模式串的next[j1]位的情况下,
        将模式串中的j2 = next[j1]+1 的位置和i再进行匹配判断的
        
        通过上述匹配过程中不断寻找曾经的自己,我们的匹配过程便得到了大大的加速和优化
        
        而获取next数组的过程我们则只需要让主串和模式串P相同再进行匹配和记录即可轻松获取
        
        下面是代码实现部分:
*/

#include <iostream>

using namespace std;

const int N = 100010,M = 1000010;

char p[N],s[M];
int ne[N];
int n,m;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    
    cin>>n>>p+1;
    cin>>m>>s+1;
    
    //求next数组
    ne[1]=0;//此步可省略,但为了便于理解,没有省略
    //错开一位匹配,是为了更好的试探
    for(int i=2,j=0;i<=n;i++)
    {
        //当退无可退时,或者寻找到了最大匹配时退出
        while(j&&p[i]!=p[j+1]) j=ne[j];
        if(p[i]==p[j+1]) j++;//若成功则匹配程度+1
        ne[i]=j;//记录最大前后缀匹配程度
    }
    
    //正式开始匹配,仍然错开一位,方便试探
    for(int i=1,j=0;i<=m;i++)
    {
        //当退无可退时,或者寻找到了最大匹配时退出
        while(j&&s[i]!=p[j+1]) j=ne[j];
        if(s[i]==p[j+1])j++;
        if(j==n)//当成功匹配最后一位时,输出结果
        {
            //题目要求下标从0开始,需注意
            cout<<i-n<<" ";
            j=ne[j];//找完后找下一个匹配
        }
    }
    return 0;
}
全部评论

相关推荐

不懂!!!:感觉你的项目描述太简单了,建议使用star描述法优化提炼一下,就是使用什么技术或方案解决了什么问题,有什么效果或成果,例如:对axios进行了二次封装,实现了请求的统一管理、错误的集中处理以及接口调用的简化,显著提高了开发效率和代码维护性,使用canvas技术实现了路线绘制功能,通过定义路径绘制函数和动态更新机制,满足了简化的导航可视化需求,提升了用户体验。像什么是使用其他组件库,基本功能描述就最好不要写到项目成果里面去了,加油
点赞 评论 收藏
分享
03-21 08:46
已编辑
门头沟学院 C++
一个什么都不会的学生:当你有硕士学历的时候HR会说就是比本科生强
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务