码迷,mamicode.com
首页 > 编程语言 > 详细

学习笔记之对KMP算法的理解

时间:2015-05-26 23:07:34      阅读:202      评论:0      收藏:0      [点我收藏+]

标签:


一,问题引入.
    有两个字符串,s[0...n-1]t[0...m-1] 在字符串s中找字符串t;
二,思考.
    对于这个问题可以逐一比较,即:

s s[p] s[p+1] s[p+2] ... s[i-1] s[i] ... ... s[n-1]
t t[0] t[1] t[2] ... t[j-1] t[j] ... t[m-1]  

s[p...i-1] == t[0...j-1],而且 s[i] != t[j], 这个时候最自然的想法是把 t 串右移一位从 t[0] 比较,即:

s s[p] s[p+1] s[p+2] ... s[i-1]  s[i] ... ... s[n-1]
t   t[0] t[1] ... t[j-2]  t[j-1] ... t[m-1]  

但是如果我们已经知道了 t[0] != s[p+1] ,那么右移一位显然是不必要的操作,类似的,若要使“右移”这一操作有意义(设右移后要和 s[i] 比较的字符是 t[k],如下表),

则,必然要在 s[p...i-1](也就是已经匹配的部分) 中找到一个字符使得 s[i-k] == t[0] , 这时,如果不幸的事情发生(在已匹配部分有 s[i-k+x] != t[x] ),那么

又是一次无意义的右移,纯属浪费精力

显然有意义的右移必须满足 s[i-k...i-1] == t[0...k-1] ,即 :

s[p] ... s[i-k] s[i-k+1] ... s[i-1] s[i] ... ... ... ... s[n-1]     
t  
    t[0] t[1] ... t[k-1] t[k] ... t[j-1] ... t[m-1]       

因为 s[p...i-1] == t[0...j-1] ,而且 s[i-k...i-1]s[p...i-1] 的后缀,所以也是 t[0...j-1] 的后缀, 又因为 t[0...k-1]t[0...j-1] 的前缀,而且,t[0...k-1] == s[i-k...i-1] 所以 t[0...k-1]t[0...j-1] 的前缀和后缀。问题又来了(真是命途多舛~_~),什么问题呢?如果对于 t[0...j-1] 有多个子串满足:既是前缀又是后缀,那应该取哪一个(k)呢 ?举个例子,t[0]t[1] == t[j-2]t[j-1]t[0...j-2] == t[1...j-1], 若选取前者,那么 j = 3, 依然可以继续比较而且是有意义的右移,如果选取后者, j = j-1, 同样是有意义的右移,这有什么差别!!差别是必须有的,假如选取后者时匹配成功,存储匹配成功时 i 的值,然后继续比较,在这个假设下,对于前者所存储的 i 的值里显然是少了,因为 前者直接右移多位,从 s[i-2...]开始比较,忽略了 s[p+1...] 匹配成功的可能;所以应该选取最长的这种子串

那么假如已经找到了 k 呢?如果 s[i] == t[k] 自然是一路愉快的比较下去咯!如果不一样,,,也不是问题,把 k 当作刚才的 j 就好了 !

由前面的分析知道,不同的 j 对应 不同的 k ,所以可以设一个 next数组,使 k = next[j] ;

如果有了 next 数组:


1
void kmp()//kuangbin模板 2 { 3 char s[maxn], t[maxn] ; 4 int next[maxn] ; 5 gets(s) ; 7 gets(t) ; 8 get_next(t,next);//获取next数组的函数 ; 9 int i = 0, j = 0 ; 10 int len_s = strlen(s), len_t = strlen(t); 11 while (i < len_s) { 12 while (j != -1 && s[i] != t[j]) j = next[j] ; //下面求next数组时把next[0] 初始化为-1,这表明t【0】匹配失败时,需要 串s 左移 13 ++ i; 14 ++ j; 15 if (j == len_t) { 16 printf("%d ",i) ;//输出匹配成功时t[m-1]在串s中的序号(下标加一) ; 17 j = next[j] ;//匹配成功了再次重新匹配,可能在 串s有多个 串t ; 18 } 19 } 20 puts("\n") ; 21 }

 

上面代码的关键是 while (j != -1 && s[i] != t[j]) j = next[j] ; s[i] != t[j] 表明 j 时 匹配失败,这时 串t 右移 ,把 j 赋值为 next[j] (可以这样考虑, k = next[j], j = k ;)

若 j = -1, 也就是 k = -1 时,显然是不可以让 j = -1 的,这说明 t[0] 就匹配失败了,

s

... s[i] ... ... s[n-1]        
t ... t[0] ... t[m-1]          

即 : s[i] != t[0] ,这时候要这样比较咯 :

s ... s[i] s[i+1] ... ... s[n-1]      
t ... ... t[0] ... t[m-1]        

即 ++ i; ++ j ;(这样 j 就 等于 0 了 ~_~) ;

新的问题来了,怎么获得这么好用的 next数组 呢 ?

三,next数组的获得.

接着分析啊!不想怎么可能知道 ~_~

突然间觉得前面一段话写错了位置:

  因为 s[p...i-1] == t[0...j-1] ,而且 s[i-k...i-1] 是 s[p...i-1] 的后缀,所以也是 t[0...j-1] 的后缀, 又因为 t[0...k-1] 是 t[0...j-1] 的前缀,而且,t[0...k-1] == s[i-k...i-1] 所以 t[0...k-1] 是 t[0...j-1] 的前缀和后缀。问题又来了(真是命途多舛~_~),什么问题呢?如果对于 t[0...j-1] 有多个子串满足:既是前缀又是后缀,那应该取哪一个(k)呢 ?举个例子,t[0]t[1] == t[j-2]t[j-1] 和 t[0...j-2] == t[1...j-1], 若选取前者,那么 j = 3, 依然可以继续比较而且是有意义的右移,如果选取后者, j = j-1, 同样是有意义的右移,这有什么差别!!差别是必须有的,假如选取后者时匹配成功,存储匹配成功时 i 的值,然后继续比较,在这个假设下,对于前者所存储的 i 的值里显然是少了,因为 前者直接右移多位,从 s[i-2...]开始比较,忽略了 s[p+1...] 匹配成功的可能;所以应该选取最长的 这种子串;

写到这里才对嘛!

首先,按照前面说的,next[0] = -1;然后不用看 串t 是什么样的都能知道 next[1] = 0(t[1]匹配失败当然要从t[0]开始啦) ;完了~_~,next[2] 看不出来了 !

 * 如果可以根据 next[1] 求 next[2] ,,,,这个还是先想想吧!容我先把 next[2] 算出来:

 1, 若 t[0] == t[1] , 那么 next[2] = 1;

 2, 若 t[0] != t[1] ,那么 next[2] = 0;

很好算嘛!Go on !

 1, 若 t[0]t[1] == t[1]t[2], 那么 next[3] = 2 ;

 2, 若 t[0]t[1] != t[1]t[2],那么 next[3] = ? 现在还不能确定,

 3,若 t[0] == t[2] ,那么 next[3] = 1 ;

 4, 若 t[0] != t[2] ,那么 next[3] = 0 ;

算next[3] 已经有点费力了(至少对我来说是这样的~_~),还是考虑前面的 * 吧!

考虑 由 next[j] 推出 next[j+1] ; 令 k = next[j] ;next[j] 表示 当 t[j] != s[i] 时,即匹配到 j 失败时 j 的新值

如表:

s s[p] ... ... ... s[i-1] s[i] ...                
t t[0] ... t[j-k] ... t[j-1] t[j] ...                
t ... ... t[0] ... t[k-1] t[k] ... t[j-1] t[j] ...          

即: t[0...k-1] == t[j-k...j-1] ;
如果 t[k] == t[j] ,那这个世界就太美好了! next[j+1] = next[j] + 1 ;
然而,,,,
好吧!看看 t[k] != t[j] 时;(CROSShh ! 再来一个表格)

t t[0] ... t[k-1] t[k] ... t[j-k] ... t[j-1] t[j] t[j+1]                  

首先要明确目标: 寻找next[j+1](kk = next[j+1]) 即 t[0...j] 最长的一个子串(既是前缀又是后缀),因为 t[k] != t[j] 所以 next[j+1] 肯定比 k 小, (然并卵!)这表明 kk < k ,也就是只能在 t[0...k]寻找一个前缀使之等于 t[j-k...j]的一个后缀,即 t[0...kk-1] == t[j+1-kk...j]  等价于 t[0...kk-2] == t[j+1-kk...j-1] && t[kk-1] == t[j] ;把重点放在前面的式子 t[0...kk-2] == t[j+1-kk...j-1]

而且 t[k-kk+1...k-1] == t[j+1-kk...j-1] (由 t[0...k-1] == t[j-k...j-1] ),所以等价于 t[0...kk-2] == t[k-kk+1...k-1] && t[kk-1] == t[j]

t t[0] ... t[kk-2] t[kk-1] ... t[k-kk+1] ... t[k-1] t[k] ... t[j] t[j+1] ...            

看前面的式子,t[0...kk-2] == t[k-kk+1...k-1] 这显然是 next[k] 啊!更新: k = next[k] 那么 kk-1 = k;  等价于判断 t[k] ?= t[j]  ~_~貌似问题回来了~不是的!是递推了!!

代码附上:

 1 void get_next(char a[],int next[])
 2 {
 3     memset(next,0,sizeof(next)) ;
 4     int k = -1, i = 0;
 5     next[0] = -1;
 6     int len = strlen(a) ;
 7     while (i < len) {
 8         while(k != -1 && a[i] != a[k]) k = next[k] ;
 9         next[++i] = ++ k;
10     }
11     for (i = 0; i < len; i ++)
12         printf("%d ",next[i]) ;
13     puts("\n") ;
14 }

 

四,小试一把。(hdu 1686 oulipo)

这是一个赤裸裸的KMP,直接给代码好了

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 const int ma = 10010 ;
 5 const int maxn = 1000010 ;
 6 int ne[ma] ;
 7 
 8 void get_next(char a[],int M)
 9 {
10     memset(ne,0,sizeof(ne)) ;
11     int k = -1, i = 0 ;
12     ne[0] = -1 ;
13     while (i < M) {
14         while (k != -1 && a[i] != a[k]) k = ne[k] ;
15         ne[++i] = ++k;
16     }
17     return ;
18 }
19 
20 int main()
21 {
22     int T ;
23     scanf("%d",&T) ;
24     char a[ma] , b[maxn] ;
25     while (T --) {
26         scanf("%s %s",a,b) ;
27         int le_a = strlen(a), le_b = strlen(b) ;
28         get_next(a,le_a) ;
29         int ans = 0 , i = 0, j = 0 ;
30         while (i < le_b && j < le_a) {
31             while (j != -1 && b[i] != a[j]) j = ne[j] ;
32             i ++ ;
33             j ++ ;
34             if (j >= le_a) {
35                 ans ++ ;
36                 j = ne[j] ;
37             }
38         }
39         printf("%d\n",ans) ;
40     }
41     return 0;
42 }

 

学习笔记之对KMP算法的理解

标签:

原文地址:http://www.cnblogs.com/smile-0/p/4531926.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!