zoukankan      html  css  js  c++  java
  • 浅谈KMP

    给你一个文本串和一个模式串,问在文本串中模式串在什么时候出现过。

    显然存在一种暴力写法(万能暴力):

    从文本串和模式串的开头进行匹配,直到失配,则从模式串开头进行重新匹配。 显然这种写法是很慢的,失配后它只能一格一格地从头开始找。

    看下面的例子:

    当匹配到以下情况:

    那么按照我们的暴力写法,应该是这个样:

    它显然是匹配不上的嘛,只能一步步接着跑了,我们当然希望程序能聪明一点.

    我们显然是希望程序避免一些无用功,最好能跳到一个最有可能匹配成功的位置,比如:

    这不就比之前笨笨的暴力写法强多了吗?

    但这仿佛是不可能的,谁能直接告诉程序应该跳到哪里呢?

    其实这个算法早有人想了出来(%%%),这就是传说中的: Knuth–Morris–Pratt algorithm(克努斯——莫里斯——普拉特算法)

    也就是KMP算法……

    它的核心思路就是能告诉程序失配后该从哪里重新匹配。

    接下来正式讲一下KMP吧。

    观察我们要跳多远:

    --->

    我们发现,如果设从k开始失配,那么我们可以保证在k之前都是一一对应的。

    在这个例子中我们发现结尾(匹配上的)有个AB,开头也有个AB。

    AB和AB是一样的,而结尾的AB与失配前文本串中匹配,所以开头的AB一定也是匹配的。

    我们考虑将模式串当前位置移到2(从0开始),那就有可能匹配上了。

    我们总结经验:失配前如果模式串开头和结尾相同,那我们就可以跳到重复子串的下一位,而不是从头匹配了。

    所以我们设k失配后应移动到next[k],则next[k]等价于在k之前(不含k)的子串中前缀和后缀相同部分的长度。

    如之前的例子就是开头结尾均有个AB长度为2,所以next[k]=2; k就应跳回位置2重新匹配。

    next数组是KMP的精髓,许多人之所以不懂KMP就是看不懂next,

    这里希望大家能结合例子搞懂next的含义。

     (良心题干)

    实现:

    第一步---求next: 就是找模式串中前缀后缀相同的长度。

    当然我们可以用现成的next来找接下来的next而不一定暴力找。

    这代码就是自己和自己匹配,k找的是之前的匹配长度,如果这两位相同,显然当前前缀后缀的匹配长度就是k+1.否则就要从头匹配了(k初值为0)

       cin>>s1>>s2;
        len1=s1.size();
        len2=s2.size();
        for(int i=1;i<len2;i++)
        {
            while(k&&s2[i]!=s2[k])k=next[k];
            if(s2[i]==s2[k])k++;
            next[i+1]=k;
        }

    第二步,匹配:

    之前求next嘛,你要保证i比k靠后,所以从1开始匹配,现在就是逐位匹配了。

    为啥输出i-len2+2 ?

    首先我们知道第i位和第k位是匹配位置结尾,所以由:

    首项=(末项-项数)*公差+1 可得初始位置为i-len2+1,而因为是从0开始计数,所以再加1.

      for(int i=0;i<len1;i++)
        {
           while(k&&s1[i]!=s2[k])k=next[k];
           if(s1[i]==s2[k])k++;
           if(k==len2)printf("%d\n",i-len2+2);
        }

    板子题

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<string>
    using namespace std;
    string s1,s2;
    int k,next[1000010],len1,len2;
    int main()
    {
        cin>>s1>>s2;
        len1=s1.size();
        len2=s2.size();
        for(int i=1;i<len2;i++)
        {
            while(k&&s2[i]!=s2[k])k=next[k];
            if(s2[i]==s2[k])k++;
            next[i+1]=k;
        }
        k=0;
        for(int i=0;i<len1;i++)
        {
           while(k&&s1[i]!=s2[k])k=next[k];
           if(s1[i]==s2[k])k++;
           if(k==len2)printf("%d\n",i-len2+2);
        }
        for(int i=1;i<=len2;i++)printf("%d ",next[i]);
    }

    总结:

    1.KMP的关键在于next数组,理解KMP的关键就是处理和应用next

    2.KMP的思路也可以用来做别的事情,比如找前缀后缀重合长度。

    3.KMP考试应用并不多,基本被人遗忘,QYF大佬有立下NOIP考KMP就吃*的flag,也是因为这一点。

    引言:

    现在哪有人用KMP做字符串匹配啊,顶多在next上跑DP什么的。

                            —— rqy

    没办法,KMP就是这么冷门,但是又引申出了它的扩展算法及改进算法(毕竟KMP是个不错的模型) 至于这些嘛......且听下回分解。

  • 相关阅读:
    从成本与职责谈测试的核心价值到底是什么
    浅谈测试媛职业发展
    Spotlight监控Oracle--Spotlight On Oracle安装和使用
    Jmeter-阶梯场景设置
    Jmeter-常用线程组设置及场景运行时间计算
    浮点数二分算法
    整数二分算法
    归并排序算法
    快速排序算法
    hadoop3.2+Centos7+5个节点主从模式配置
  • 原文地址:https://www.cnblogs.com/zzh666/p/8996135.html
Copyright © 2011-2022 走看看