数据结构与算法——串的模式匹配

模式匹配——KMP算法

算法思路

算法产生的原因

由于Brute-Force算法在模式串对主串匹配的过程中,要不断对主串指针进行回溯,实际上有些情况下主串指针的回溯是不必要的,例如:
算法过程对比
当模式串在第四个字符与主串失配时,BF的做法是将主串指针回溯,模式串从头开始和主串的下一个字符处开始匹配。这里可以是注意模式串和主串在失配位置前有相同的字符,此时再从头重新匹配是没有必要的。

因此KMP算法就是为加速匹配有用信息,减少不必要的指针回溯,提高算法效率而产生。

KMP算法是D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的,称之为Knuth-Morris-Pratt算法,简称KMP算法。

算法步骤

1. 从模式串t中提取加速匹配的信息

我们需要模式串 t t t中每一个字符 t j ( 0 ≤ j ≤ m − 1 ) t_j(0\leq j\leq m-1) tj(0jm1) 前是否存在一个整数 k ( k < j ) k(k<j) k(k<j),使得 t j t_j tj 前的k个字符 ( t j − k , t t − k + 1 , . . . , t j − 1 ) (t_{j-k},t_{t-k+1},...,t_{j-1}) (tjk,ttk+1,...,tj1)与模式串 t t t开头的 k k k个字符 ( t 0 , t 1 , . . . , t k − 1 ) (t_0,t_1,...,t_{k-1}) (t0,t1,...,tk1)相等。这样在模式串的某位与主串失配时,可将模式串从该位对应的 t k t_k tk开始,重新与失配处匹配。

用数组 n e x t [ j ] next[j] next[j]表示 t j t_j tj对应的最大 k k k值, n e x t [ j ] = m a x { k } next[j]=max\lbrace k\rbrace next[j]=max{k}
n e x t [ j ] = { − 1 M A X { k ∣ 0 < k < j 且 " t 0 t 1 . . . t k − 1 " = " t j − k t j − k + 1 . . . t j − 1 " } 0 next[j]=\left\{ \begin{array}{l} -1\\ MAX\lbrace k|0<k<j且"t_0t_1...t_{k-1}"="t_{j-k}t_{j-k+1}...t_{j-1}"\rbrace \\ 0 \end{array} \right. next[j]= 1MAX{k∣0<k<j"t0t1...tk1"="tjktjk+1...tj1"}0

n e x t [ j ] next[j] next[j]求解过程:
(1) n e x t [ 0 ] = − 1 next[0]=-1 next[0]=1 n e x t [ 1 ] = 0 next[1]=0 next[1]=0(不考虑 j j j或1~ j − 1 j-1 j1的位置没有字符的情况);
(2)若 " t 0 t 1 . . . t k − 1 " = " t j − k t j − k + 1 . . . t j − 1 " "t_0t_1...t_{k-1}"="t_{j-k}t_{j-k+1}...t_{j-1}" "t0t1...tk1"="tjktjk+1...tj1",有 n e x t [ j ] = k next[j]=k next[j]=k
(3)若 t j = t k t_{j}=t_{k} tj=tk,即 " t 0 t 1 . . . t k " = " t j − k t j − k + 1 . . . t j − 1 t j " "t_0t_1...t_k"="t_{j-k}t_{j-k+1}...t_{j-1}t_j" "t0t1...tk"="tjktjk+1...tj1tj",有 n e x t [ j + 1 ] = k + 1 next[j+1]=k+1 next[j+1]=k+1
(4)若 t j ≠ t k t_{j}\neq t_{k} tj=tk,说明 t j + 1 t_{j+1} tj+1前不存在长度为 n e x t [ j ] + 1 next[j]+1 next[j]+1的子串与开头字符起的子串相同,此时将 k k k回退到 k ′ = n e x t [ k ] k'=next[k] k=next[k],即比较 t n e x t [ k ] ( t k ′ ) t_{next[k]}(t_{k '}) tnext[k](tk) t j t_j tj是否相等,直到回退到不存在可匹配的子串,则 n e x t [ j + 1 ] = 0 next[j+1]=0 next[j+1]=0;若 t j ≠ t k t_{j}\neq t_{k} tj=tk,置 k = n e x t [ k ] k=next[k] k=next[k]

2. 模式匹配过程

当主串 s s s和模式串 t t t在对应 t j t_j tj s i s_i si位置失配时,根据 n e x t [ j ] = k next[j]=k next[j]=k的值,直接将模式串从头和主串 s i s_i si位置的前 k k k处对其,直接重新比较 t k t_k tk s i s_i si是否相等,当失配移动使得位置取到 n e x t [ 0 ] = − 1 next[0]=-1 next[0]=1,表示当前已经没有字符可与 s i s_i si比较,则下一次比较从 s i + 1 s_{i+1} si+1 t 0 t_0 t0开始比较。
匹配过程

过程描述:
i=0;j=0;
while(s和t都没扫描完)
{ if(就或者当前两个串指向的字符相等)
i和j分别增1;
else
i不变,j回退到j=next[j] (模式串右滑)
}
if(j超界)返回i-t的长度 //模式匹配成功
else 返回 -1 //模式匹配失败

代码演示

模式串的next数组

void GetNext(String t,int next[])//由模式串t求出next数组
{
    int j,k;//j扫描模式串t
    j=0;k=-1;//k记录t[j]之前与模式串开头起相同子串的字符个数
    next[0]=-1;//设置next[0]的值
    while(j<t.length-1) //求模式串t所有位置的next值
    {
      if(k==-1||t.data[j]==t.data[k])
      {
        k++;j++;
        next[j]=k;
      }
      else k=next[k];
    }
}

KMP模式匹配过程

int KMPIndex(String s,String t)//KMP算法(返回匹配位置的首位)
{
   int next[MaxSize],i=0;j=0;
   GetNext(t,next);
   while(i<s.length&&j<t.length)
   {
     if(j==-1||s.data[i]==t.data[j])
     {i++;j++;}
     else j=next[j];       //模式串右滑
   }
   if(j>=t.length)return (i-t.length);     //匹配成功
   else return (-1);                       //匹配失败
}

优化

考虑到当 t j t_j tj s i s_i si失配时,若 t j = t k t_j=t_k tj=tk,则也没有必要在下一次匹配时将模式串右移 k k k位(将 t k t_k tk s i s_i si比较),而是直接将 s i s_i si t n e x t [ k ] t_{next[k]} tnext[k]比较,以此类推。由此引入 n e x t v a l [ j ] nextval[j] nextval[j]数组。

n e x t v a l [ j ] nextval[j] nextval[j]的定义是 n e x t v a l [ 0 ] = − 1 nextval[0]=-1 nextval[0]=1,当 t j = t n e x t [ j ] t_j=t_{next[j]} tj=tnext[j]时, n e x t v a l [ j ] = n e x t v a l [ n e x t [ j ] ] nextval[j]=nextval[next[j]] nextval[j]=nextval[next[j]],否则 n e x t v a l [ j ] = n e x t [ j ] nextval[j]=next[j] nextval[j]=next[j]

n e x t v a l nextval nextval取代 n e x t next next,得到优化后的KMP算法如下:

  • 由模式串求出 n e x t v a l nextval nextval
void GetNextval(String t,int nextval[])//由模式串t求出nextval数组
{
    int j,k;//j扫描模式串t
    j=0;k=-1;//k记录t[j]之前与模式串开头起相同子串的字符个数
    nextval[0]=-1;//设置nextval[0]的值
    while(j<t.length-1) //求模式串t所有位置的next值
    {
      if(k==-1||t.data[j]==t.data[k])
      {
        k++;j++;
        if(t.data[j]!=t.data[k])  //主要修改部分
          nextval[j]=k;
        else 
          nextval[j]=nextval[k];
     
      }
      else k=nextval[k];
    }
}
  • 匹配过程
int KMPIndex(String s,String t)//优化后KMP算法(返回匹配位置的首位)
{
   int nextval[MaxSize],i=0;j=0;
   GetNextval(t,nextval);
   while(i<s.length&&j<t.length)
   {
     if(j==-1||s.data[i]==t.data[j])
     {i++;j++;}
     else j=nextval[j];       //模式串右滑
   }
   if(j>=t.length)return (i-t.length);     //匹配成功
   else return (-1);                       //匹配失败
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值