zoukankan      html  css  js  c++  java
  • 【字符串】KMP算法

    KMP算法

    参考
    感谢草叶y提出本文原先存在的错误

    基本概念

    1、s[ ]是模式串,即比较长的字符串(要去匹配上的字符串)。
    2、p[ ]是模板串,即比较短的字符串。(用来去匹配的字符串)
    3、“非平凡前缀”:指除了最后一个字符以外,一个字符串的全部头部组合(前面连续的部分)。
    4、“非平凡后缀”:指除了第一个字符以外,一个字符串的全部尾部组合。(后面会有例子,均简称为前/后缀)

    注意:KMP算法中前后缀指的都是非平凡,即不包括整个串。

    5、“部分匹配值”:前缀和后缀的最长共有元素的长度。
    6、next[ ]是“部分匹配值表”,即next数组,它存储的是每一个下标对应的“部分匹配值”,是KMP算法的核心。(后面作详细讲解)

    next数组的含义、手动模拟及求解

    用p[x,y]来表示p串中位置起于x位置,终于y位置的子串。

    对于任意一个j,且j小于等于p串的长度

    有这么一个定义,next[j]表示的是p[1,j]子串中前缀和后缀相同的最大子串的长度。

    为了算法的方便,KMP算法中字符串p的下标从1开始

    按照定义,对于位置j,有这么一个性质,p[1,next[j]]=p[j-next[j]+1,j](即前缀等于后缀)

    比如,abaaba,我们可以对它进行手动模拟

    手动模拟

    p a b a a b
    下标 1 2 3 4 5
    nextp[下标+1] 0 0 1 1 2

    对于next[1]来说,p串下标1前是没有任何元素的

    子串 a
    非平凡前缀 (empty)
    非平凡后缀 (empty)
    next[1] 0

    对于next[2]来说,

    子串 ab
    非平凡前缀 {a}
    非平凡后缀 {b}
    next[2] 0

    对于next[3]来说,

    子串 aba
    非平凡前缀 {a,ab}
    非平凡后缀 {a,ba}
    next[3] 1

    对于next[4]来说,

    子串 abaa
    非平凡前缀 {a,ab,aba}
    非平凡后缀 {b,ba,baa}
    next[4] 1

    对于next[5]来说,

    子串 abaab
    非平凡前缀 {a,ab,aba,abaa}
    非平凡后缀 {b,ab,aab,baab}
    next[5] 2

    经过手动的模拟,我们有了大概的对KMP算法next数组的求解规律的感觉。

    next数组的求解

    • 有模式串去生成next数组

    • 在已经知道next[j]的前提下,尝试去求next[j+1],可以发现next[j+1]<=next[j]+1.

    • 由于求的是非平凡前后缀,所以KMP算法的求next数组的起始位置要从2开始。

    • 由于是求一个前缀和一个后缀,故而,需要两个指针来完成任务。(一个用来确定前缀,一个用来确定后缀)。

    • 因为p[1,next[j]]=p[j-next[j]+1,j],设前子串为a,后子串为b,由于前后子串相等,且a子串内部也有对应的next[ a.length() ] ,且它会和b子串对应上。

    • 这个性质在KMP快速匹配的时候非常有用(可以看成前缀和后缀各自裂开,最终形成了四个相同部分),比如说这一句(i代表新加入来的点,而j代表的是原来为加入i这个点时的串p[1,i-1]的next[i-1])。

      while(j&&p[i]!=p[j+1])j=next[j];
      

      大致流程

    image

    image

    image

    image

    image

    代码理解,如果有必要的话就做一个裂开处理,然后裂开完后,再做进一步的确认

    for(int i = 2, j = 0; i <= m; i++)
    {
        while(j && p[i] != p[j+1]) j = next[j];
        if(p[i] == p[j+1]) j++;
        next[i] = j;
    }
    

    处理好产生于较小长度的p串的next数组后,就可以拿着这个next数组再s串中进行匹配。

    匹配

    这里也是需要分两个指针,指针i和指针j,指针i用来在s串中扫,指针j用来在p串中扫。

    同时在匹配过程中,如果没有出现什么意外(匹配不上)的话,匹配量就会一直加一加一,并且

    for(int i = 1 , j=0;i<=s.length();i++)
    {
         while(j && s[i]!=p[j+1]) j=ne[j];
         if(s[i]==p[j+1]) j++;
         if(j==m)//匹配成功
         {
              printf("%d",i-m+1)
              j = ne[j];
         }
    }
    
    

    重新开始匹配之旅且借助next数组快速开局(重生之我是kmp匹配)

    image
    image
    image

    #include <bits/stdc++.h>
    using namespace std;
    inline int read()
    {
        int x=0,f=1;
        char ch=getchar();
        while(ch<'0'||ch>'9')
        {
            if(ch=='-')
                f=-1;
            ch=getchar();
        }
        while(ch>='0' && ch<='9')
            x=x*10+ch-'0',ch=getchar();
        return x*f;
    }
    const int N = 1E6+10, M =1E5+10; 
    int n,m;
    int ne[M];
    char s[N],p[M];
    int main()
    {
        m = read();
        cin>>p+1;
        n = read();
        cin>>s+1;
        for(int i=2,j=0;i<=m;i++)
        {
        	while(j&&p[i]!=p[j+1]) j = ne[j];
        	if(p[i]==p[j+1]) j++;
        	ne[i] = j;
    	}
    	for(int i=1,j=0;i<=n;i++)
    	{
    	
    		while(j&&s[i]!=p[j+1]) j=ne[j];
    		if(s[i]==p[j+1]) j++;
    		if(j==m)
    		{
    			printf("%d ",i-m);
    			j = ne[j];
    		}
    		
    	}
        return 0;
    }
    
    
  • 相关阅读:
    TDateTime 的相关用法
    Delphi 2005 之后的版本如何装组件
    (收藏)《博客园精华集》分类索引
    用 IIS 7、ARR 與 Velocity 建设高性能的大型网站
    异常处理准则
    Linq之动态排序(字符传入)
    用存储过程构造一个虚拟日期表发现的趣事
    Linq to SQL 加注Data Annotation在 Asp.Net MVC2中的应用
    .net framework加密方法
    SQL Server到Oracle连接服务器
  • 原文地址:https://www.cnblogs.com/BeautifulWater/p/15430316.html
Copyright © 2011-2022 走看看