初识kmp hdu 1686

一,kmp 模板

前缀函数 即next函数

数组下标从0开始


void Prefix_Func() { int i,k; k=-1; next[0]=-1; for(i=1;i<m;i++) { while(k>=0 && P[k+1]!= P[i]) k=next[k]; if(P[k+1] == P[i]) k++; next[i]=k; } }

 

匹配算法

 int Kmp()
 {
     int i,k;
     int cnt=0;
     k=-1;
     for(i=0;i<n;i++)
     {
        while(k>=0 && P[k+1]!= T[i])
            k=next[k];
        if(P[k+1] == T[i])
            k++;
        if(k == m-1)
        {
            //cnt++;
            k=next[k];
        }
     }
     //return cnt;
 }

 

二,理论介绍

模板 T,模式Pattern ,模式长度为m

关于模式的前缀函数;

1:模式P的前缀函数的定义,是函数∏:{1,2,……,m}→ {0,1,……,m-1}, 满足  ∏[q]=max{k:k<q 且 P⊐ Pq}。。。。。。。。。。。。可知 ∏[q] < q 

2: w ⊐ x,w是x的后缀  ,w ⊏ x , w是x的前缀。  符号⊏  ,竖线指向是头

3: 定义: ∏*[q]= {∏[q] , (2)[q] ,  ∏(3)[q],……,  ∏(t)[q]}   ,其中 ∏(i)[q]是按函数的迭代的概念来定义的,满足  ∏(0)[q]=q,并且对i≥ 1 , ∏(i)[q]= ∏[ ∏(i-1)[q] ], 当达到 ∏(t)[q] =0 时,序列终止。

4:引理1

*[q]  =  {k: k<q 且  P⊐ Pq}

5:引理2

如果  ∏[q] >0 , 则  ∏[q]  - 1  ∈   ∏*[q-1]

6: 定义子集 Eq-1 = {k:k∈  ∏*[q-1],且 P[k+1] = P[q] }   = {k: k<q-1, Pk+1 Pq }  

其中     ∏*[q-1]=    {∏[q-1] ,  ∏(2)[q-1] ,  ∏(3)[q-1],……,  ∏(t)[q-1]}   

7: 推论1

对q = 2 ,3,...,m      有    , ∏[1]  = 0 ,因为要求∏[q] < q。

前缀函数的伪代码:

COMPUTE=PREFIX-FUNCTION(P)

   m=P.length

  let  ∏[1,...,m] be a bew array

  ∏[1]=0       

  k=0                                                 

  for q 2 to m

    while k>0 and P[k+1]‡P[q]

        k=∏[k]

    if P[k+1]==P[q]

        k=k+1

    ∏[q] = k

 

 分析:

1:for 循环每次迭代开始时,k= ∏[q-1] ,初始 时,  ∏[1]=0 故令k=0

while 循环搜索所有 k∈  ∏*[q-1] ,直到找到一个k,使得  P[k+1]==P[q] ,此时的k也是满足条件的最大的。k是集合Eq-1 中的最大值, 然后设置 ∏[q]  = k+1 ,由推论可知。

2,这也算是一个递推,首项 ∏[1]=0  ,然后 由公式        递推。

 

三,  kmp算法


 

用字符串匹配自动机来比喻说明:

字符串匹配自动机 M =(Q,q0  ,A, Σ , δ) 五元组。

Q 是状态的 集合  {0 ,1 ,..., m} 

q0  表示初始状态   q0  = 0

A 是一个特殊的接受状态集合   此时的 A= {m}

Σ   模板和模式  的元素都是来自 字母集 Σ

δ  转移函数  δ(q, a)= σ(Pa)  

其中 ,q 是表示读完T之后,自动机所处状态, 也是表示 P的前缀和T后缀的最长匹配长度。


 kmp 算法伪代码

KMP-MATCHER(T ,P)
  n=T.length
  m=P.length
  COMPUTER-PREFIX-FUNCTION(P) // 计算模式的前缀函数
  k=0
  for i=1 to n
      while k>0 and P[k+1] != T[i] //搜索所有k'  ∈ ∏*[k]   , 找出满足  P[k+1] == T[i]  的最大的前缀k,然后继续匹配, 否则 k走到状态0 
        k=∏[k]
      if P[k+1] == T[i]
        k=k+1
      if k==m        状态k ==m  字符被接受
        printf""
        q=∏[q] // 避免在找到P的一次后,出现 P[m+1]错误,所以需要计算m的前缀

 

k表示自动机的状态,读完T之后,自动机所处状态, 也是表示 P的前缀和T后缀的最长匹配长度。  

状态转移公式 %%%%%%%%%%%%%%%%

   if  P[k + 1] = T'[i]     k' = k + 1  

 if  P[k + 1] != T'[i]        字符不能继续匹配,   这时我们必须找到一个更小的字串   k'   ∈ ∏*[k]  ,满足P[k + 1] = T'[i]      ,就停在k'这个状态,进行P[k' + 1] = T'[i]  匹配, 不然, k状态走完变成0 .

 

hdu  1686 代码

 1 #include<iostream>
 2 #include<stdio.h>
 3 #include<string>
 4 #include<string.h>
 5 #define N 1000005  //模板长度
 6 #define M 10005  //模式长度
 7 using namespace std;
 8 char T[N];      //
 9 char P[M];     //
10 int next[M];     // 前缀函数值 next = pi
11 int n,m;
12 
13 //求前缀函数
14  void Prefix_Func()
15  {
16      int i,k;   
17      k=-1;                  //下标从 0开始 的 , 故 k = -1  满足 next[k]<k   ,循环第一次是i= 1 ,故满足 next[0]  = -1
18      next[0]=-1;
19      for(i=1;i<m;i++)  //    for循环入口条件是 next[i-1] = k 
20      {
21         while(k>=0 && P[k+1]!= P[i])        //证明过的函数 循环体内,要么就是找到 k  满足 P[k+1] = P[i] ,要么就是走到了 状态 -1 
22             k=next[k];
23         if(P[k+1] == P[i])    
24             k++;
25         next[i]=k;
26      }
27  }
28  int Kmp()
29  {
30      int i,k;
31      int cnt=0;
32      k=-1;   // 初始状态为 -1 
33      for(i=0;i<n;i++)
34      {
35         while(k>=0 && P[k+1]!= T[i])     // 状态转移公式     循环结束 k=-1 或者 P[k+1] = =  T[i]
36             k=next[k];
37         if(P[k+1] == T[i])
38             k++;
39         if(k == m-1)
40         {
41             cnt++;
42             //k=next[k];
43         }
44      }
45      return cnt;
46  }
47 int main()
48 {
49     int t;
50     cin>>t;
51     while(t--)
52     {
53         scanf("%s",P);
54         scanf("%s",T);
55         n=strlen(T);
56         m=strlen(P);
57         Prefix_Func();
58         printf("%d\n",Kmp());
59     }
60     return 0 ;
61 }

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值