【笔记】串的模式匹配算法_设目标串 t="ababcabcda",模式串 p="abcd",则简单模式匹配算法中直至匹配成功,-程序员宅基地

技术标签: 基础整理  KMP算法  字符串匹配  模式匹配算法的实现  BF算法  数据结构  模式匹配  

  串的模式匹配也称为子串的定位操作,即查找子串在主串中出现的位置。设有主串S和子串T,如果在主串S中找到一个与子串T相相等的串,则返回串T的第一个字符在串S中的位置。其中,主串S又称为目标串,子串T又称为模式串。本文主要介绍两种常用的模式匹配算法,即朴素模式匹配算法——BF算法和改进算法——KMP算法。


一、BF算法

1.BF算法思想

  BF算法思想是从主串 S=s0s1sn1 的第pos个字符开始与模式串 T=t0t1tm1 的第一个字符比较,如果相等则继续逐个比较后续字符;否则从主串的下一个字符开始重新与模式串T的第一个字符比较,依此类推。如果在主串S中存在与模式串T相等的连续字符序列,则匹配成功,函数返回模式串T中第一个字符在主串中S中的位置;否则函数返回-1表示匹配失败。
  假设主串为S=“ababcabcacbab”,模式串T=“abcac”,S的长度为n=13,T的长度为m=5。用变量i表示主串S中当前正在比较字符的下标,变量j表示子串T中当前正在比较字符的下标。模式匹配过程如下图所示。


这里写图片描述

  BF算法简单且容易理解,并且进行某些文本处理时,效率也比较高;然后在有些情况下,则效率很低。下面分析其时间复杂性。设串S长度为n,串T长度为m。匹配成功的情况下,考虑两种极端情况:

  • 最好情况下

  在最好情况下,每趟不成功的匹配都发生在第一对字符比较时。例如:

s=aaaaaaaaaabc,t=bc

  设匹配成功发生在 si 处,则字符比较次数在前面i-1趟匹配中共比较了i-1次,第i趟成功的匹配共比较了m次,所以总共比较了i-1+m次,所有匹配成功的可能共有n-m+1种。设从 si 开始与t串匹配成功的概率为 pi ,在等概率的情况下 pi=1nm+1 ,因此最好情况下平均比较的次数是
1nm+1i=1nm+1(i1+m)=12(n+m)

即最好情况下的时间复杂性是 O(n+m)


  • 最坏情况下

  在最坏情况下,每趟匹配不成功的匹配都发生在t的最后一个字符。例如:

s=aaaaaaaaaaab,t=aaab

  设匹配成功发生在 si 处,则在前面i-1趟匹配中共比较了(i-1)*m次,第i趟成功的匹配共比较了m次,所以总共比较了i*m次,共需要进行n-m+1趟比较。因此最坏情况下,的平均比较次数是
1nm+1i=1nm+1im=m(nm+2)2

即最坏情况下的时间复杂性是 O(nm)


2.BF算法实现(C语言)

  假设串采用顺序存储方式存储,则BF匹配算法如下:

int B_FIndex(SeqString S,int pos,SeqString T)
/*BF模式匹配算法。在主串S中的第pos个位置开始查找子串T,如果找到返回子串在主串的位置;否则,返回-1*/
{
    int i,j;
    i=pos-1;
    j=0;
    while(i<S.length&&j<T.length)
    {
        if(S.str[i]==T.str[j])      /*如果串S和串T中对应位置字符相等,则继续比较下一个字符*/
        {
            i++;
            j++;
        }
        else                    /*如果当前对应位置的字符不相等,则从串S的下一个字符开始,T的第0个字符开始比较*/
        {
            i=i-j+1;
            j=0;
        }
    }
    if(j>=T.length)                 /*如果在S中找到串T,则返回子串T在主串S的位置*/
        return i-j+1;
    else
        return -1;
}

  在BF算法中,即使主串与模式串已有多个字符经过比较相等,只要有一个字符不相等,就需要将主串的比较位置回退。


二、KMP算法

  KMP算法在BF算法的基础上有较大的改进,可在O(m+n)时间数量级上完成串的模式匹配,主要是消除了主串指针的回退,使算法效率有了很大程度的提高。

1.KMP算法思想

  KMP算法的基本思想是在每一趟匹配过程中出现字符不等时,不需要回退主串的指针,而是利用已经得到前面“部分匹配”的结果,将模式串向右滑动若干个字符后,继续与主串中的当前字符进行比较。
  假设主串S=“ababcabcacbab”,模式串T=“abcac”,KMP算法匹配过程如下左图所示。


这里写图片描述

  从图中可以看出,KMP算法的匹配次数由BF算法的6趟减少为3趟。在整个KMP算法中,主串i指针没有回退。

  下面讨论一般情况。假设主串 S=s0s1sn1 ,模式串 T=t0t1tm1 。在模式匹配过程中,如果出现字符不匹配的情况,即当 SiTj(0i<n,0j<m) 时,有

sijsij+1si1=t0t1tj1

  假设子串即模式串存在可重叠的真子串,即
t0t1tk1=tjktjk+1tj1

  即子串中存在从 t0 开始到 tk1 与从 tjk tj1 的重叠子串。根据上面两个等式,可以得出 siksik+1si1=t0t1tk1 ,如上右图所示。

  匹配过程中,当主串中的第i+1个字符与模式串中的第j+1个字符不等时,仅需将模式串向右滑动至第k+1个字符与主串中的第i+1个字符对齐,即 si tj 对齐, 此时,模式串中子串 t0t1tk1 必定与主串中的子串 siksik+1si1 相等,因此匹配只需从第i+1个字符与模式串中的第k+1个字符开始比较。
  如果令next[j]=k,则next[j]表示当模式串中的第j个字符与主串中的对应的字符不相等时,在模式串中需要重新与主串中与该字符进行比较的字符的位置。模式串中的next函数定义如下:


这里写图片描述

  其中第一种情况,next[j]的函数是为了方便算法设计而定义的;第二种情况,如果子串中存在重叠的真子串,则next[j]的取值就是k,即模式串的最长子串的长度;第三种情况,如果模式串中不存在重叠的子串,则从子串的第一个字符开始比较。

  KMP算法的模式匹配过程:如果模式串T中存在真子串 t0t1tk1=tjktjk+1tj1 ,当模式串T的 tj 与主串S的 si 不相等,则按照next[j]=k将模式串向右滑动,从主串中的 si 与模式串的 tk 开始比较;如果 si=tk ,则主串与模式串的指针各自增1,继续比较下一个字符;如果 sitk ,则按照next[next[j]]将模式串继续向右滑动,将主串中的 si 与模式串中的next[next[j]]字符进行比较;如果仍然不相等,则按照以上方法,将模式串继续向右滑动,直到next[j]=-1。这时,模式串不再向右滑动,从 si+1 开始与 t0 进行比较。

  利用next函数值的一个模式匹配示例如下图所示。


这里写图片描述


2.next函数的算法

  • 求next函数值

  模式串中的next函数值的取值与主串无关,仅与模式串有关,根据模式串next函数定义,next函数值可用递推的方法得到。
  设next[j]=k,表示在模式串T中存在以下关系:

t0t1tk1=tjktjk+1tj1

  其中, 0<k<j ,k为满足等式的最大值,即不可能存在 k>k 满足以上等式。则计算next[j+1]的值会出现两种情况:
  (1)如果 ti=tk ,则表示在模式串T中满足关系 t0t1tk=tjktjk+1tj ,并且不可能存在 k>k 满足以上等式。因此有next[j+1]=k+1,即next[j+1]=next[j]+1。
  (2)如果 titk ,则表示在模式串T中满足关系 t0t1tktjktjk+1tj 。在这种情况下,可以把求next函数值的问题看成一个模式匹配的问题。目前已经有 t0t1tk1=tjktjk+1tj1 ,但是 titk ,把模式串T向右滑动到k’=next[k]。
  如果有 tj=tk ,则表示模式串中有 t0t1tk=tjktjk+1tj ,因此有next[j+1]=k’+1,即next[j+1]=next[k]+1。
  如果有 tjtk ,则将模式串继续向右滑动到第next[k’]个字符与 tj 比较。如果仍不相等,则将模式串继续向右滑动到下表为next[next[k’]]字符与 tj 比较。依此类推,直到 tj 和模式串中某个字符匹配成功或不存在任何 k(1<k<j) 满足 t0t1tk=tjktjk+1tj ,则有next[j+1]=0。

  • 改进的求next函数值算法

  一般地,在求得next[j]=k后,如果模式串中的 tj=tk ,则当主串中的 sitk 时,不必再将 si tk 比较,而是直接与 tnext[k] 比较。因此可以将求next函数值的算法进行修正,即在求得next[j]=k后,判断 tj 是否等于 tk ,如果相等,还需继续将模式串向右滑动,使k’=next[k],判断 tj 是否等于 tk ,直到两者不等为止。


3.KMP算法的实现(C语言)

  • KMP模式匹配算法
      利用模式串T的next函数值求T在主串S中的第pos个字符之后的位置的KMP算法描述如下:
int KMP_Index(SeqString S,int pos,SeqString T,int next[])
/*KMP模式匹配算法。利用模式串T的next函数在主串S中的第pos个位置开始查找子串T,如果找到返回子串在主串的位置;否则,返回-1*/
{
    int i,j;
    i=pos-1;
    j=0;
    while(i<S.length&&j<T.length)
    {
        if(j==-1||S.str[i]==T.str[j])       /*如果j=-1或当前字符相等,则继续比较后面的字符*/
        {
            i++;
            j++;
        }
        else                        /*如果当前字符不相等,则将模式串向右移动*/
            j=next[j];
    }
    if(j>=T.length)                     /*匹配成功,返回子串在主串中的位置。否则返回-1*/
        return i-T.length+1;
    else
        return -1;
}
  • 求next函数值的算法:
int GetNext(SeqString T,int next[])
/*求模式串T的next函数值并存入数组next*/
{
    int j,k;
    j=0;
    k=-1;
    next[0]=-1;
    while(j<T.length)
    {
        if(k==-1||T.str[j]==T.str[k])       /*如果k=-1或当前字符相等,则继续比较后面的字符并将函数值存入到next数组*/
        {
            j++;
            k++;
            next[j]=k;
        }
        else                        /*如果当前字符不相等,则将模式串向右移动继续比较*/
            k=next[k];
    }
}

  求next函数值的算法时间复杂度是O(m)。一般情况下,模式串的长度比主串的长度要小得多,因此对整个字符串的匹配来说,增加这点事件是值得的。

  • 改进的求next函数值的算法:
int GetNextVal(SeqString T,int nextval[])
/*求模式串T的next函数值的修正值并存入数组next*/
{
    int j,k;
    j=0;
    k=-1;
    nextval[0]=-1;
    while(j<T.length)
    {
        if(k==-1||T.str[j]==T.str[k])       /*如果k=-1或当前字符相等,则继续比较后面的字符并将函数值存入到nextval数组*/
        {
            j++;
            k++;
            if(T.str[j]!=T.str[k])          /*如果所求的nextval[j]与已有的nextval[k]不相等,则将k存放在nextval中*/
                nextval[j]=k;
            else
                nextval[j]=nextval[k];
        }
        else                                /*如果当前字符不相等,则将模式串向右移动继续比较*/
            k=nextval[k];
    }
}

三、模式匹配应用举例

  编程比较BF算法和KMP算法的效率。例如主串S=“cabaadcabaababaabacabababab”,模式串T=“abaabacababa”,统计BF算法和KMP算法在匹配过程中的比较次数,并输出模式串的next函数值与nextval函数值。

  • 主函数
/*包含头文件*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"SeqString.h"
/*函数的声明*/
int B_FIndex(SeqString S,int pos,SeqString T,int *count);
int KMP_Index(SeqString S,int pos,SeqString T,int next[],int *count);
int GetNext(SeqString T,int next[]);
int GetNextVal(SeqString T,int nextval[]);
void PrintArray(SeqString T,int next[],int nextval[],int length);
void main()
{
    SeqString S,T;
    int count1=0,count2=0,count3=0,find;
    int next[40],nextval[40];

    StrAssign(&S,"cabaadcabaababaabacabababab");        /*给主串S赋值*/
    StrAssign(&T,"abaabacababa");               /*给模式串T赋值*/
    GetNext(T,next);                    /*将next函数值保存在next数组*/
    GetNextVal(T,nextval);              /*将改进后的next函数值保存在nextval数组*/
    printf("模式串T的next和改进后的next值:\n");
    PrintArray(T,next,nextval,StrLength(T));    /*输出模式串T的next值与nextval值*/
    find=B_FIndex(S,1,T,&count1);               /*传统的模式串匹配*/
    if(find>0)
        printf("BF算法的比较次数为:%2d\n",count1);
    find=KMP_Index(S,1,T,next,&count2);
    if(find>0)
        printf("利用next的KMP算法的比较次数为:%2d\n",count2);
    find=KMP_Index(S,1,T,nextval,&count3);
    if(find>0)
        printf("利用nextval的KMP匹配算法的比较次数为:%2d\n",count3);

    StrAssign(&S,"abccbaaaababcabcbccabcbcabccbcbcb");  /*给主串S赋值*/
    StrAssign(&T,"abcabcbc");                   /*给模式串T赋值*/
    GetNext(T,next);                            /*将next函数值保存在next数组*/
    GetNextVal(T,nextval);                      /*将改进后的next函数值保存在nextval数组*/
    printf("模式串T的next和改进后的next值:\n");
    PrintArray(T,next,nextval,StrLength(T));    /*输出模式串T的next值域nextval值*/
    find=B_FIndex(S,1,T,&count1);               /*传统的模式串匹配*/
    if(find>0)
        printf("BF算法的比较次数为:%2d\n",count1);
    find=KMP_Index(S,1,T,next,&count2);
    if(find>0)
        printf("利用next的KMP算法的比较次数为:%2d\n",count2);
    find=KMP_Index(S,1,T,nextval,&count3);
    if(find>0)
        printf("利用nextval的KMP匹配算法的比较次数为:%2d\n",count3);
}

void PrintArray(SeqString T,int next[],int nextval[],int length)
/*模式串T的next值与nextval值输出函数*/
{
    int j;
    printf("j:\t\t");
    for(j=0;j<length;j++)
        printf("%3d",j);
    printf("\n");
    printf("模式串:\t\t");
    for(j=0;j<length;j++)
        printf("%3c",T.str[j]);
    printf("\n");
    printf("next[j]:\t");
    for(j=0;j<length;j++)
        printf("%3d",next[j]);
    printf("\n");
    printf("nextval[j]:\t");
    for(j=0;j<length;j++)
        printf("%3d",nextval[j]);
    printf("\n");
}
  • BF算法
int B_FIndex(SeqString S,int pos,SeqString T,int *count)
/*BF模式匹配算法。在主串S中的第pos个位置开始查找子串T,如果找到返回子串在主串的位置;否则,返回-1*/
{
    int i,j;
    i=pos-1;
    j=0;
    *count=0;                       /*count保存主串与模式串的比较次数*/
    while(i<S.length&&j<T.length)
    {
        if(S.str[i]==T.str[j])      /*如果串S和串T中对应位置字符相等,则继续比较下一个字符*/
        {
            i++;
            j++;
        }
        else                    /*如果当前对应位置的字符不相等,则从串S的下一个字符开始,T的第0个字符开始比较*/
        {
            i=i-j+1;
            j=0;
        }
        (*count)++;
    }
    if(j>=T.length)                 /*如果在S中找到串T,则返回子串T在主串S的位置*/
        return i-j+1;
    else
        return -1;
}
  • KMP算法
int KMP_Index(SeqString S,int pos,SeqString T,int next[],int *count)
/*KMP模式匹配算法。利用模式串T的next函数在主串S中的第pos个位置开始查找子串T,如果找到返回子串在主串的位置;否则,返回-1*/
{
    int i,j;
    i=pos-1;
    j=0;
    *count=0;                               /*count保存主串与模式串的比较次数*/
    while(i<S.length&&j<T.length)
    {
        if(j==-1||S.str[i]==T.str[j])       /*如果j=-1或当前字符相等,则继续比较后面的字符*/
        {
            i++;
            j++;
        }
        else                        /*如果当前字符不相等,则将模式串向右移动*/
            j=next[j];
        (*count)++;
    }
    if(j>=T.length)                     /*匹配成功,返回子串在主串中的位置。否则返回-1*/
        return i-T.length+1;
    else
        return -1;
}
int GetNext(SeqString T,int next[])
/*求模式串T的next函数值并存入数组next*/
{
    int j,k;
    j=0;
    k=-1;
    next[0]=-1;
    while(j<T.length)
    {
        if(k==-1||T.str[j]==T.str[k])       /*如果k=-1或当前字符相等,则继续比较后面的字符并将函数值存入到next数组*/
        {
            j++;
            k++;
            next[j]=k;
        }
        else                        /*如果当前字符不相等,则将模式串向右移动继续比较*/
            k=next[k];
    }
}
int GetNextVal(SeqString T,int nextval[])
/*求模式串T的next函数值的修正值并存入数组next*/
{
    int j,k;
    j=0;
    k=-1;
    nextval[0]=-1;
    while(j<T.length)
    {
        if(k==-1||T.str[j]==T.str[k])       /*如果k=-1或当前字符相等,则继续比较后面的字符并将函数值存入到nextval数组*/
        {
            j++;
            k++;
            if(T.str[j]!=T.str[k])          /*如果所求的nextval[j]与已有的nextval[k]不相等,则将k存放在nextval中*/
                nextval[j]=k;
            else
                nextval[j]=nextval[k];
        }
        else                                /*如果当前字符不相等,则将模式串向右移动继续比较*/
            k=nextval[k];
    }
}

  朴素的BF算法也是常用的算法,毕竟它不需要计算next函数值。KMP算法在模式串与主串存在许多部分匹配的情况下,其优越性才会显示出来。

  • 测试结果

这里写图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cbwem/article/details/78307642

智能推荐

计算机丢失concrt140,小编教你解决concrt140 dll 【解决教程】 的技巧_-程序员宅基地

文章浏览阅读4.5w次。近日有小伙伴发现电脑出现问题了,在突然遇到concrt140 dll时不知所措了,对于concrt140 dll带来的问题,其实很好解决concrt140 dll带来的问题,下面小编跟大家介绍concrt140 dll解决方法:丢失CONCRT140.dll,怎么办?答:分析及解决:网上下载这个DLL文件,将其放置到system32目录下面。 重启系统,或者在CMD下面运行regsvr32*.dl..._concrt140.dll下载教程

微信小程序源码案例大全_微信小程序switch页面demo-程序员宅基地

文章浏览阅读4.3k次,点赞4次,收藏62次。微信小程序demo:足球,赛事分析 小程序简易导航 小程序demo:办公审批 小程序Demo:电魔方 小程序demo:借阅伴侣 微信小程序demo:投票 微信小程序demo:健康生活 小程序demo:文章列表demo 微商城(含微信小程序)完整源码+配置指南 微信小程序Demo:一个简单的工作系统 微信小程序Demo:用于聚会的小程序 微信小程序Demo:Growth 是一款..._微信小程序switch页面demo

SLAM学习笔记(Code2)----刚体运动、Eigen库_eigen.determinant-程序员宅基地

文章浏览阅读2.2k次。2.1除了#include<iostream>之外的头文件#include <Eigen/Core>//Core:核心#include <Eigen/Dense>//求矩阵的逆、特征值、行列式等#include <Eigen/Geometry>//Eigen的几何模块,可以利用矩阵完成如旋转、平移/***其他***/#include <ctime>//可用于计时,比较哪个程序更快#include <cmath>//包含a_eigen.determinant

图像梯度-sobel算子-程序员宅基地

文章浏览阅读1w次,点赞12次,收藏61次。(1)理论部分x 水平方向的梯度, 其实也就是右边 - 左边,有的权重为1,有的为2 。若是计算出来的值很大 说明是一个边界 。y 竖直方向的梯度,其实也就是下面减上面,权重1,或2 。若是计算出来的值很大 说明是一个边界 。图像的梯度为:有时简化为:即:(2)程序部分函数:Sobelddepth 通常取 -1,但是会导致结果溢出,检测不出边缘,故使..._sobel算子

cuda10.1和cudnn7.6.5百度网盘下载链接(Linux版)_cudnn7.6网盘下载-程序员宅基地

文章浏览阅读3.6k次,点赞17次,收藏8次。cuda10.1和cudnn7.6.5百度网盘下载链接(Linux版)在官网下载不仅慢,,,主要是还总失败。。终于下载成功了,这里给出百度网盘下载链接,希望可以帮到别人百度网盘下载链接提取码: vyg5_cudnn7.6网盘下载

Python正则表达式大全-程序员宅基地

文章浏览阅读9.3w次,点赞69次,收藏427次。定义:正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。上面都是官方的说明,我自己的理解是(仅供参考):通过事先规定好一些特殊字符的匹配规则,然后利用这些字符进行组合来匹配各种复杂的字符串场景。比如现在的爬虫和数据分析,字符串校验等等都需要用_python正则表达式

随便推点

NILM(非侵入式电力负荷监测)学习笔记 —— 准备工作(一)配置环境NILMTK Toolkit_nilmtk学习-程序员宅基地

文章浏览阅读1.9w次,点赞27次,收藏122次。安装Anaconda,Python,pycharm我另一篇文章里面有介绍https://blog.csdn.net/wwb1990/article/details/103883775安装NILMTK有了上面的环境,接下来进入正题。NILMTK官网:http://nilmtk.github.io/因为官方安装流程是基于linux的(官方安装流程),我这里提供windows..._nilmtk学习

k8s-pod 控制器-程序员宅基地

文章浏览阅读826次,点赞20次,收藏28次。如果实际 Pod 数量比指定的多那就结束掉多余的,如果实际数量比指定的少就新启动一些Pod,当 Pod 失败、被删除或者挂掉后,RC 都会去自动创建新的 Pod 来保证副本数量,所以即使只有一个 Pod,我们也应该使用 RC 来管理我们的 Pod。label 与 selector 配合,可以实现对象的“关联”,“Pod 控制器” 与 Pod 是相关联的 —— “Pod 控制器”依赖于 Pod,可以给 Pod 设置 label,然后给“控制器”设置对应的 selector,这就实现了对象的关联。

相关工具设置-程序员宅基地

文章浏览阅读57次。1. ultraEdit设置禁止自动更新: 菜单栏:高级->配置->应用程序布局->其他 取消勾选“自动检查更新”2.xshell 传输文件中设置编码,防止乱码: 文件 -- 属性 -- 选项 -- 连接 -- 使用UTF-8编码3.乱码修改:修改tomcat下配置中,修改: <Connector connectionTimeou..._高级-配置-应用程序布局

ico引入方法_arco的ico怎么导入-程序员宅基地

文章浏览阅读1.2k次。打开下面的网站后,挑选要使用的,https://icomoon.io/app/#/select/image下载后 解压 ,先把fonts里面的文件复制到项目fonts文件夹中去,然后打开其中的style.css文件找到类似下面的代码@font-face {font-family: ‘icomoon’;src: url(’…/fonts/icomoon.eot?r069d6’);s..._arco的ico怎么导入

Microsoft Visual Studio 2010(VS2010)正式版 CDKEY_visual_studio_2010_professional key-程序员宅基地

文章浏览阅读1.9k次。Microsoft Visual Studio 2010(VS2010)正式版 CDKEY / SN:YCFHQ-9DWCY-DKV88-T2TMH-G7BHP企业版、旗舰版都适用推荐直接下载电驴资源的vs旗舰版然后安装,好用方便且省时!) MSDN VS2010 Ultimate 简体中文正式旗舰版破解版下载(附序列号) visual studio 2010正_visual_studio_2010_professional key

互联网医疗的定义及架构-程序员宅基地

文章浏览阅读3.2k次,点赞2次,收藏17次。导读:互联网医疗是指综合利用大数据、云计算等信息技术使得传统医疗产业与互联网、物联网、人工智能等技术应用紧密集合,形成诊前咨询、诊中诊疗、诊后康复保健、慢性病管理、健康预防等大健康生态深度..._线上医疗的定义

推荐文章

热门文章

相关标签