KMP

KMP

acw831

时间复杂度:O(m+n)

属于双指针算法的一种

思想:找到"特性"而后借此优化,由于BF算法中已匹配的子串部分的信息是确定的且该部分等同于相匹配的模式串子串,进行移动时可以只靠模式串的性质减少匹配的次数

1.next列表的理解

可能的前缀集合

可能的前缀指:当p[0,t)==p[j-t,j),对于是模式串子串的前缀与后缀相同的前缀,但不是整个子串的前缀

next列表只取决于模式串,因为在遇到不匹配字符时,需要移动模式串,当p[0,t)==p[j-t,j),对于模式串是前缀与后缀相同,因此只要进行这种前缀的终点t移动到该不匹配字符再次匹配的移动

避免回溯

而这种前缀有多个,选择最长的可以避免回溯,移动它之后可以再次移动不会漏掉可能的

2.next列表的生成

  1. 对每个字符为结尾,模式串第一个字符为开头的子串进行匹配
  2. 对于第i个字符的子串使用已经匹配1-j个字符进行匹配
  3. j<=i-1,因此匹配满足”可能的前缀“的条件,即前缀长度<=子串长度-1
  4. 第i个字符的对应的子串的前缀长度l<=第i-1个的长度+1,这个反证法可得,并且,第i个字符的对应的子串的前缀可以由i-1个字符nex[i-1]集合中使得p[j+1]==p[i]的前缀得到,因为该前缀满足“可能的前缀定义"且是符合条件中最长的,因此nex[i]=j+1
   nex[1]=0;//第一个字符不存在前后缀相同但不是整个子串的前缀
  for(int i=2,j=0;i<=n;i++)//j表示已匹配最长前缀1-j,若为0则是表示当前无匹配的前缀
    {
        while(j&&p[i]!=p[j+1])//若i-1的已匹配最长前缀1-j,第j+1个字符和p[i]不相同,则调用next列表寻找符合的前缀
        j=nex[j];
        if(p[i]==p[j+1])//跳出循环有两种原因,满足后者的猜得到next列表表达前缀的末尾字符下标j
        {
            j++;
        }
        nex[i]=j;
    }

3.利用next列表进行匹配

for(int i=1,j=0;i<=m;i++)
    {
        while(j&&s[i]!=p[j+1])//和得到next列表的过程很类似,但是模式串与文本串的匹配,若不匹配则调用next列表快捷移动,迭代移动
        {
            j=nex[j];
        }
       if(s[i]==p[j+1])//匹配则增加已匹配长度,匹配下一个字符
        j++;
        if(j==n)//匹配完备输出
        {
        printf("%d ",i-n);//要求从0开始,而我的从1开始,i-n+1-1
            //输出匹配的子串下标
        j=nex[j];//如果不提前调用next列表可能会在下一次尝试匹配的时越界,j+1>n
            
        }
    }

完整代码

#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
char p[MAXN],s[MAXN];
int nex[MAXN];
int main()
{
    int n,m;
    cin>>n>>p+1>>m>>s+1;
    nex[1]=0;
    for(int i=2,j=0;i<=n;i++)
    {
        while(j&&p[i]!=p[j+1])
        j=nex[j];
        if(p[i]==p[j+1])
        {
            j++;
        }
        nex[i]=j;
    }
    
    for(int i=1,j=0;i<=m;i++)
    {
        while(j&&s[i]!=p[j+1])
        {
            j=nex[j];
        }
       if(s[i]==p[j+1])
        j++;
        if(j==n)
        {
        printf("%d ",i-n);
        j=nex[j];
            
        }
    }
    
    return 0;
}
posted @ 2022-01-13 17:55  多巴胺不耐受仿生人  阅读(66)  评论(0)    收藏  举报