hihocoder1015 KMP算法

KMP算法是字符串模式匹配的一种经典算法。

设原串为s[],模式串为p[]。

1. 朴素的算法

枚举匹配起始位置 i(s中),从起始位置 i 开始,与模式串中的字符逐次一一对比,直到匹配成功或者匹配失败, 然后++i,如此循环直到遍历完原串s。

2. KMP算法

2.1 出发点

  对于某次匹配,如果第一个字符就没有匹配成功,那么跟朴素算法一样,将起始位置加1, 再继续做;但是,如果有一部分字符匹配成功了,我们是否可以利用这些匹配成功的子串信息?实际上利用这些信息我们可以做到两点优化,从而得到经典的KMP算法,具体见2.2.

2.2 KMP的核心思路

  首先介绍两个概念:前缀和后缀。举例来说,对于字符串“ababa”, 它的前缀包括"a", "ab", "aba", "abab", 后缀包括"baba", "aba", "ba", "a"。

  KMP的核心在于一个叫做next数组的东西,next数组中到底装的是什么呢?其实很简单,next[i]表示的是s[0...i]的前缀集合和后缀集合中最长的公共串的长度。在上面的例子“ababa”中,前缀集合和后缀集合中,公共串有两个,分别为长度为1的“a”和长度为3的“aba”,因此对应的next值为next[4] = 3(下标从0开始)。

  有了next数组,我们就可以做到上面提到的两点优化了。

  第一点优化:在枚举匹配起始位置的时候,每次移动的不再是1,而是移动到使得某个前缀和后缀相重合的位置,这里所提提到的某个前缀就是next值中所对应的那个公共串。如下所示:

下标 0 1 2 3 4
a b a b a
next值 0 0 1 2 3

  假设模式串为"aba", 匹配的步骤为(注意起始位置变化):

  (1) a b a b a

      a b a

    此时,匹配了前3个字符,于是起始位置向后移动3 - next[2] = 2, 而不是朴素匹配算法中的移动1.

  (2)a b a b a

        a b a

    匹配结束。

  第二点优化:在匹配的时候,不再是像朴素算法中那样每次从模式串中的第0个字符开始匹配,因为有了next数组,我们现在已经知道模式串中的一部分已经匹配了(next值的对应公共串),所以,就没必要再从第0个开始,而是从next值所对应的公共串的下一个字符开始。比如上面的步骤(2), 不会再从模式串的第0个字符"a"开始与原串进行匹配,而是从第1个字符"b"开始。

2.3 next数组的求解步骤

  (1) 显然next[0] = 0, 因为一个字符不具备任何前缀和后缀;

  (2) 对于i>0, next[i]的计算:

    令k = next[i-1],此时我们已经知道p[0,...,k-1]==p[i-k,...i-1], 且p[0,...,k-1]就是p[0,...,i-1]的前后缀集合中的那个最长公共串。

    如果p[i]==p[k],那么显然有next[i] = k+1。

    但当p[i]!=p[k]的时候我们该怎么办呢?此时next[i]的值显然不会超过k即next[i-1],也就是说,这种情况下我们要找的那个公共串,要么不存       在,next[i]=0, 要么是next[i-1]对应的那个公共串的一个前缀,此时我们需要找到p[0,...,i-1]的一个较短的前后缀公共串,使得p[i]==p[k](k为所求较短公共  串的长度),那么,我们该如何来找到上述那个较短的公共串呢?我们此时要找的那个较短公共串,是p[0,...i-1]的长度为k=next[i-1]的前缀的一个前缀和长度  为k=next[i-1]的后缀的一个后缀,而我们知道p[0,...i-1]中长度为k=next[i-1]的前缀和后缀是相同的,因此,我们找那个较短串的问题就转换为p[0,...,k-1]  的最长公共前后缀问题了,现在就可以用递归来求得那个较短串的长度。写成代码就一个简单循环:while(k!=0&&p[i]!=p[k]) k = next[k-1].

    关于next数组求解的问题,我觉得这篇文章写的很好,比较容易懂,链接在此:http://blog.csdn.net/yearn520/article/details/6729426

最后,附上我的实现代码(题目来源:http://hihocoder.com/problemset/problem/1015)

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 #define MAX_PATTERN 10005
 7 #define MAX_ORIGINAL 1000005
 8 
 9 char pat_str[MAX_PATTERN];
10 char ori_str[MAX_ORIGINAL];
11 int next[MAX_PATTERN];
12 
13 void getNext()
14 {
15     int len = strlen(pat_str);    
16     next[0] = 0;
17     for(int i=1; i<len; ++i)
18     {
19         int k = next[i-1];
20         while(k!=0 && pat_str[i]!=pat_str[k])
21             k = next[k-1];
22         if(pat_str[i]==pat_str[k]) next[i] = k+1;
23         else next[i] = 0;
24     }
25 }
26 
27 int kmp(char *s, char *p)
28 {
29     int cnt = 0;
30     int len_ori = strlen(ori_str);
31     int len_pat = strlen(pat_str);
32     getNext();
33     int i = 0, j = 0;
34     for(int k=0; k<len_ori; )
35     {
36         while(i<len_ori&&j<len_pat&&ori_str[i]==pat_str[j])
37         {
38             ++i;
39             ++j;
40         }
41         if(j==len_pat)
42         {
43             ++cnt;
44         }
45         if(j==0)
46         {
47             ++k;
48             j = 0;
49             i = k;    
50         }
51         else
52         {
53             k += j-next[j-1];
54             i = k+next[j-1];
55             j = next[j-1];
56         }
57     }
58     return cnt;
59 }
60 
61 int main()
62 {
63     int n;
64     scanf("%d", &n);
65     while(n--)
66     {
67         scanf("%s%s", pat_str, ori_str);
68         printf("%d
", kmp(ori_str, pat_str));
69     }
70     
71     return 0;
72 }

 

  

   

 

原文地址:https://www.cnblogs.com/pczhou/p/4279528.html