• #1015 : KMP算法


    时间限制:1000ms
    单点时限:1000ms
    内存限制:256MB

    描述

    小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

    这一天,他们遇到了一只河蟹,于是河蟹就向小Hi和小Ho提出了那个经典的问题:“小Hi和小Ho,你们能不能够判断一段文字(原串)里面是不是存在那么一些……特殊……的文字(模式串)?

    小Hi和小Ho仔细思考了一下,觉得只能想到很简单的做法,但是又觉得既然河蟹先生这么说了,就肯定不会这么容易的让他们回答了,于是他们只能说道:“抱歉,河蟹先生,我们只能想到时间复杂度为(文本长度 * 特殊文字总长度)的方法,即对于每个模式串分开判断,然后依次枚举起始位置并检查是否能够匹配,但是这不是您想要的方法是吧?”

    河蟹点了点头,说道:”看来你们的水平还有待提高,这样吧,如果我说只有一个特殊文字,你能不能做到呢?“

    小Ho这时候还有点晕晕乎乎的,但是小Hi很快开口道:”我知道!这就是一个很经典的模式匹配问题!可以使用KMP算法进行求解!“

    河蟹满意的点了点头,对小Hi说道:”既然你知道就好办了,你去把小Ho教会,下周我有重要的任务交给你们!“

    ”保证完成任务!”小Hi点头道。

    输入

    第一行一个整数N,表示测试数据组数。

    接下来的N*2行,每两行表示一个测试数据。在每一个测试数据中,第一行为模式串,由不超过10^4个大写字母组成,第二行为原串,由不超过10^6个大写字母组成。

    其中N<=20

    输出

    对于每一个测试数据,按照它们在输入中出现的顺序输出一行Ans,表示模式串在原串中出现的次数。

    样例输入

    5
    HA
    HAHAHA
    WQN
    WQN
    ADA
    ADADADA
    BABABB
    BABABABABABABABABB
    DAD
    ADDAADAADDAAADAAD
    样例输出
    3
    1
    3
    1
    0
    提示和题目连接:http://hihocoder.com/problemset/problem/1015
    解答:
    通过查阅资料发现了四种进行字符串匹配的算法,分别为朴素算法、Rabin-Karp、有限自动机算法、Knuth-Morris-Pratt算法

    设定已知条件:已知模式P[1..m]需要匹配的文本T[1..n]
    首先说朴素算法,朴素算法真的很朴素,就是通过一个循环来寻找有效的移位,该循环对n-m+1个可能的每一个移位值s进行匹配检查P[1..m]=T[s+1..s+m]
    算法伪码:
    NAIVE-STRING-MATCHER(T,P)
        n  <- length[T]
        m <- length[P]
        for  s  <- 0 to n-m
            do if P[1..m] = T[s+1..s+m]
                    then print"找到一个匹配的模式"
    

    下面暂时略过Rabin-Karp算法,回头补上,

    有限自动机算法:

      这个算法的步骤就是根据模式P生成一个状态变迁函数,也可以绘出一个状态变迁图

    例如模式P = {ababaca}就是可以定义7个状态

    注:对于长度为n的字符串模式,一般定义n+1个状态,其中包含一个初始状态和一个接受状态,在接受状态意味着一个匹配的成功。

    继续上面模式P的变迁图建立:

      这个是对应于模式P={ababaca}的状态转换图,图中浅蓝的标号0的点为起始状态,红色的标号为0的点为接受状态,图中未画出的输入对应的线均指向状态0.

      在状态0位置,输入a时,此时字符串a的最长后缀对应P的最长前缀字符串a,长度为1,状态转移到1;输入b时,对应的最长前缀字符串长度为0,c时也是

      在状态1位置,输入a时,此时字符串aa的最长后缀对应P的最长前缀字符串为a,长度为1,状态转移到1;输入b时,此时字符串ab的最长后缀对应P的最长前缀字符串为ab,长度为2,所以状态转移到2;当输入c时,字符串ac的最长后缀对应P的最长前缀字符串为空,长度为0,状态转移到0。

      ......

      在状态7位置,到达接受状态,即此时已经出现了一次成功匹配,开始下一轮,再输入一个字符a时状态转移到1,输入b时状态转移到2

    上述即为有限状态机的建立过程,由此可知,当对应模式P的有限状态机建立后,对于文本T的判断是线性的,只需要对于每一个字符输入后的状态变迁,每次到达接受状态就完成一次成功的匹配。

    KMP算法:

      KMP算法的关键在于找到模式P的前缀函数next。

      在此以模式P={ababababca}为例,阐述一下KMP前缀函数的建立意义。

    考察朴素的字符串匹配算法的操作过程,当上述模式当中前四个字符匹配成功后,如果第五个字符匹配失败,说明第文本中对应的第五个字符不是a(建议在纸上画一下),还说明了对应的四个字符为abab,将P右移一个位置发现仍然不匹配,右移两个、三个、四个也是,但是右移五个未必。因此能否不像上述方法那样一步一步右移,而是直接右移五个位置开始进行判断。

    其实在上面匹配过程当中,每次成功的匹配就包含了一定量的信息,而分析模式当中ab的重复也可给人以匹配失败后,可否每次移动两个位置再进行匹配的启发,这样充分发掘模式本身的特点可以建立一个next函数,从而确定每次匹配失败后移动的长度。

    对于上述模式P发掘的next函数为

    伪代码如下:

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <string>
    #include <vector>
    using namespace std;
    
    int KMP(string t, string p){
      int n = p.size();
      vector <int> next(n+1, 0);
      for(int i=1; i<n; i++){
        int j=i; 
        while(j > 0){
          j = next[j];
          if(p[j] == p[i]){
            next[i+1] = j + 1;
            break;
          }
        }
      }
    
      int ans = 0;
      int m = t.size();
      for(int i=0, j=0; i<m; i++){
        if(j < n && t[i] == p[j])  j++;
        else{
          while(j > 0){
            j = next[j];
            if(t[i] == p[j]){
              j++;
              break;
            }
          }
        }
        if(j == n) 	ans ++;
      }
      return ans;
    }
    
    int main(){
      string t, p;
      int n;
      scanf("%d", &n);
      while(n--){
        cin>>p>>t;
        cout<<KMP(t, p)<<endl;
      }
      return 0;
    }
    

      



    态度决定高度,细节决定成败,
  • 相关阅读:
    [offer_53-1] 在排序数组中查找数字 I (开启编辑看 i,j,m)
    window10 办公软件word、execel、ppt突然变得很卡顿如何解决?
    数组中第k大的数
    heapq 堆
    每日一题 482. 密钥格式化
    算法笔记Go!
    DFS与BFS的python实现
    无向图中找到长度为k的“链”
    无序数组中找一个比左边都大、右边都小的数
    SRM(空域富模型隐写分析)
  • 原文地址:https://www.cnblogs.com/lxk2010012997/p/4781899.html
Copyright © 2020-2023  润新知