• [学习笔记]KMP


    KMP算法

    我的腿让我停下,可是心却不允许我那么做。

    • 引言
      字符串匹配。给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置.
    char *str = "bacbababadababacambabacaddababacasdsd";  
    char *ptr = "ababaca";
    
    • 暴力解法

    如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;
    如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。

    来看看时间复杂度:最坏情况下为O(n*m)
    所以有没有一种改进的算法

    • 改进方法
      可以实现复杂度为O(m+n),为何简化了时间复杂度:
      KMP算法主要是取消了指针的回溯,充分利用了目标字符串ptr的性质(比如里面部分字符串的重复性,即使不存在重复字段,在比较时,实现最大的移动量)。每趟匹配过程中出现字符比较不等时,不回溯主指针i,利用已得到的“部分匹配”结果将模式向右滑动尽可能远的一段距离,继续进行比较。
      具体概念上的我不想深究,从代码开始理解

    代码

    • KmpSearch函数

      • 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
        • 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
        • 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
          换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数为:j - next[j],且此值大于等于1。
    • 所以next 数组各值的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表j 之前的字符串中有最大长度为k 的相同前缀后缀。
      此也意味着在某个字符失配时,该字符对应的next 值会告诉你下一步匹配中,模式串应该跳到哪个位置(跳到next [j] 的位置)。如果next [j] 等于0或-1,则跳到模式串的开头字符,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某个字符,而不是跳到开头,且具体跳过了k 个字符。

    int KmpSearch(char* s, char* p)  
    {  
        int i = 0;  
        int j = 0;  
        int sLen = strlen(s);  
        int pLen = strlen(p);  
        while (i < sLen && j < pLen)  
        {  
            //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++      
            if (j == -1 || s[i] == p[j])  
            {  
                i++;  
                j++;  
            }  
            else  
            {  
                //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]      
                //next[j]即为j所对应的next值        
                j = next[j];  
            }  
        }  
        if (j == pLen)  
            return i - j;  
        else  
            return -1;  
    }  
    

    • getnext()函数
      • (1) next[0] = -1;
      • (2) 设next[j] = k,则next[j+1] = ?
        令j=j+1,k=k+1;
        • 若pk=pj,则有“p1…pk-1pk”=“pj-k+1…pj-1pj” ,
          next[j]=k;
        • 若pk+1≠pj+1,可把求next值问题看成是一个模式匹配问题,整个模式串既是主串,又是子串。
          即使得k=next[k],回溯求得最长前缀等于最长后缀的下标
    j 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    模式串 0 a b c a a b b c a b c a a b d a
    next[j] -1 0 0 0 1 1 2 0 0 1 2 3 4 5 6 0 1
    void getnext(int*next,char*ctr){
        next[0]=-1;
        int j=0,k=-1,len=strlen(ctr);
        while(j<len){
            if(k==-1||ctr[j]==ctr[k]){
                j++;
                k++;
                next[j]=k;
            }
            else k=next[k];
        }
    }
    

    完整代码

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+7;
    int a[maxn],b[maxn];
    int nxt[10005],n,m;
    void getnext(){
        int i=0,j=-1;
        nxt[0]=-1;
        while(i<m){
            if(j==-1||b[i]==b[j]){
                i++,j++;
                if(b[i]==b[j])nxt[i]=nxt[j];
                else nxt[i]=j;
            }
            else j=nxt[j];
        }
    }
    int kmp(){
        int i=0,j=0;
        getnext();
        while(i<n){
            if(a[i]==b[j]||j==-1)i++,j++;
            else j=nxt[j];
            if(j==m)return i-j+1;
        }
        return -1;
    }
    int main(){
        int t;
        scanf("%d",&t);
        while(t--){
            scanf("%d%d",&n,&m);
            for(int i=0;i<n;i++)scanf("%d",&a[i]);
            for(int i=0;i<m;i++)scanf("%d",&b[i]);
            if(n<m)printf("-1
    ");
            else printf("%d
    ",kmp());
        }
        return 0;
    }
    

    练习

    1. 牛客三 E

    题目:给你一个字符串S,你要对字符串S的每一位i将前i位的字符串移动到尾部形成一个新的字符串,如果形成的字符串相同则归为一类Li。现在让你将Li类按照字典序排序,并让你输出每一类的数量和每一类中字符串对应的下标i

    很多人用哈希,正解是KMP的next数组的运用.通过观察,题目问的就是求字符串的循环节,而next数组就是前后缀的运用。然后有个结论:如果对于一个长度为L的字符串,如果L%(L-next[L])==0则代表它具有循环节,且循环节的长度为L-next[L]。

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+7;
    char b[maxn];
    int net[maxn],len;
    template<class T>
    void read(T &res)
    {
        res = 0;
        char c = getchar();
        T f = 1;
        while(c < '0' || c > '9')
        {
            if(c == '-') f = -1;
            c = getchar();
        }
        while(c >= '0' && c <= '9')
        {
            res = res * 10 + c - '0';
            c = getchar();
        }
        res *= f;
    }
    template<class T>
    void out(T x)
    {
        if(x < 0)
        {
            putchar('-');
            x = -x;
        }
        if(x >= 10)
        {
            out(x / 10);
        }
        putchar('0' + x % 10);
    }
    void get_next(char*ctr)
    {
        net[0]=-1;
        int j=0,k=-1;
        len=strlen(ctr);
        while(j<len)
        {
            if(k==-1||ctr[j]==ctr[k])
            {
                j++;
                k++;
                net[j]=k;
            }
            else k=net[k];
        }
    }
    int main()
    {
        scanf("%s",b);
        get_next(b);
        int tmp=len-net[len];
        if(len%tmp==0&&tmp!=len)
        {
            out(tmp);
            puts(" ");
            for(int i=0; i<tmp; i++)
            {
                out(len/tmp);
                printf(" ");
                for(int j=i;j<len;j+=tmp){
    
                    out(j);
                    //
                    printf(" ");
                }
                printf("
    ");
            }
        }
        else{
            out(len);
       // puts(" ");
             printf("
    ");
            for(int i=0;i<len;i++){
                    out(1);
                    printf(" ");
                out(i);
                printf("
    ");
            }
    
        }
        //
        return 0;
    }
    
    
    不要忘记努力,不要辜负自己 欢迎指正 QQ:1468580561
  • 相关阅读:
    qt setfixedsize以后怎么让窗口可正常resize
    总线操作使用工具
    DTK 获取活动色
    qt 设置背景色
    python_itchat模块登陆问题
    python_基础知识
    python_爬虫_str类型的html文本去标签
    工作__问题
    服务器_sshfs的安装、挂载、取消挂载
    python_爬虫_通过selenium获取人人网cookie值并模拟登陆个人界面
  • 原文地址:https://www.cnblogs.com/smallocean/p/9385645.html
Copyright © 2020-2023  润新知