zoukankan      html  css  js  c++  java
  • 【KMP算法(烤馍片,真香)】

    KMP

    KMP算法,又称烤馍片算法,是字符串匹配的改良算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。

    我们就在一步一步的实践探索中来理解这个神奇的算法吧

    说到字符串匹配,就是给你两个串,看一个文本串里是否含有一个模式串(文本串为root,模式串为s)

    暴力算法

    暴力算法我们都会,一位一位的比较嘛,不一样向后移动就行了

    下面是一个例子

    我们拿到这个串的时候,就先让s[1]和root[1]对齐(下标从1开始)

    当前四位都匹配成功的时候(abca)我们匹配第五位,第五位不匹配,按照暴力的算法我们就会让s[1]和root[2]对齐

    这样的话roo里面的指针就会回退,这是对已知信息的极大的浪费,

    我们有没有一种方法能利用已匹配的信息让root的指针不回退,从而让s(模式串)直接移动到我们想要的位置呢?

    答案是有的,那就是烤馍片算法

    KMP算法

    如何操作

    我们再拿样例来分析一下

     我们匹配好前面四位的时候,发现第五位不对,我们就移动模式串

    根据前面已知的,我们就可以像下面这样

     直接向后移动两位,然后我们的roo指针继续从第五位开始,直到匹配成功

    这里有一个前缀串和后缀串的知识

    如abcde

    前缀串:a,ab,abc,abcd,abcde

    后缀串:e,de,cde,bcde,abcde

    我们要求它们除了自己本身的最长公共前后缀

    例如这里,除了本身就没有了

    现在不懂没关系,接着往下

    我们发现,当匹配不成功的时候,我们root指针所指的位置前面都是匹配的

    如上面的abab

    这里的最长公共前后缀(除了自己)就是ab

    所以我们就可以一次性移动两位

    但是这样会不会有中间漏掉的情况呢?(下面的......是省略其中的字符)

    例如这个,我们现在已知匹配了的它除了自己的最长公共前后缀是***

    当我们前面的都匹配完了:

    匹配下一位的时候,发现不对,我们就可以直接把模式串开头的下标向后移动到

     我们假设其中有一种情况

     使得这样可以匹配成功(root指针还是在已匹配的那里)

    这里就相等

    由之前可知

     这两个相等

    所以最长公共前后缀(除去他自己)就是框起来的玩意儿了,但是我们已知的并不是那个,所以这是矛盾的,所以也就不需要担心上述情况的发生了

    通过这里,我们就可以解释一下为什么要除去他自己了,因为我们移动是从最长公共前后缀的前缀移动到后缀,但是如果算上自己的话,相当于没有移动,这就是没有意义的。懂?

    我们就可以用一个next数组来存储每一个区间的最长公共前后缀的长度了

    再往回看,发现没有,匹配成功的都是模式串的前缀串,所以我们的next数组只需要记录模式串前缀串的最长公共前后缀了

    既然理解了KMP的操作方式,我们来看看怎么实现

    如何实现

    我们next[i]表示模式串1到i的最长公共前后缀的长度

     首先next[1]为0(这个很好理解吧,就一个字符,除去自己就没了)

    我们再看第二位,我们现在有两个指针,一个i指当前处理的位置,然后一个j表示1---i-1的最长公共前后缀的长度。为什么要j呢?

    你想想,我们前面已经匹配好了,如果新加入进来的,也就是i指针所指的,和已知最长的下一位相等,我们不就可以避免重头计算而是很好的利用了

     这里i和j的下一位不相等,所以就是

    next[2]=j

    相等

    j++

    next[i]=j

    相等

    j++

    next[i]=j=2

    j++

    next[i]=j=3

     

     到这里不相等了,意思是我们只能继续寻找比现在长度小的公共前后缀了,但是怎么找呢?

    这个例子不明显,我们换一个

     这是已经处理的,现在j等于3,表示当前除了本身的最长公共前后缀长度为3

     但是这样的话,就不能继续了,我们就要找第二长的公共前后缀了,但是怎么找?

    我们来假设一下

     

     现在这是已知的长度为x

    当我们想找到一个长度小于x的时(黄色部分)由于这个的长度小于x并且两个黑框框相等,所以我们就可以合并

     代换过来就是这样,现在就在同一个框框里了,所以我们只需要求next[x]的最长的公共前后缀就行了,懂?不懂得可以私信的,这里是最难的地方

    下面就是代码

     1 void getnext()
     2 {
     3     int j=0;//初始为0 
     4     for(int i=2;i<=n;i++)
     5     {                        //这里就是查找第二长的 回退操作 
     6         while(j!=0&&s[i]!=s[j+1])j=next[j];//如果j等于0的话,我们直接比较就行了,因为不存在第二长的 
     7         next[i]=(s[j+1]==s[i]?++j:j);//判断是否相等 
     8     }
     9     return ;
    10 }

    我们康康全局吧

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int N=1000010;
     4 int n;
     5 int next[N];
     6 char root[N],s[N];
     7 vector<int> ans; 
     8 void getnext()
     9 {
    10     int j=0;//初始为0 
    11     for(int i=2;i<=n;i++)
    12     {                        //这里就是查找第二长的 回退操作 
    13         while(j!=0&&s[i]!=s[j+1])j=next[j];//如果j等于0的话,我们直接比较就行了,因为不存在第二长的 
    14         next[i]=(s[j+1]==s[i]?++j:j);//判断是否相等 
    15     }
    16     return ;
    17 }
    18 void kmp()
    19 {
    20     int j=0;
    21     int len1=strlen(root+1);
    22     int len2=strlen(s+1);
    23     for(int i=1;i<=len1;i++)
    24     {
    25         while(j!=0&&root[i]!=s[j+1]){j=next[j];} 
    26         if(root[i]==s[j+1])j++;//j是已匹配的长度 
    27         if(j==len2)ans.push_back(i-len2+1),j=next[j];//长度达到了就记录,但是我们还要继续找, 所以回退 
    28     }
    29 }
    30 int main()
    31 {
    32     scanf("%s%s",root+1,s+1);//输入 
    33     n=strlen(s+1);//记录长度 
    34     getnext();//整理next数组 
    35     kmp();//比较 
    36     for(int i=0;i<ans.size();i++)
    37         printf("%d
    ",ans[i]);
    38     cout<<next[1];
    39     for(int i=2;i<=n;i++)
    40         printf(" %d",next[i]);
    41     return 0;
    42 }

    好的,一份精美的烤馍片就做好了!!!

  • 相关阅读:
    hadoop的运行模式
    集群之间配置 SSH无密码登录
    NameNode故障处理方法
    HDFS的HA(高可用)
    DataNode的工作机制
    NameNode和SecondaryNameNode的工作机制
    HDFS读写数据流程
    Linux软件包管理
    DNS服务之二:Bind97服务安装配置
    ssl协议、openssl及创建私有CA
  • 原文地址:https://www.cnblogs.com/hualian/p/12313433.html
Copyright © 2011-2022 走看看