#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;
}

  



posted @ 2015-09-04 18:15  再见,少年  Views(939)  Comments(0Edit  收藏  举报