技术标签: 基础整理 KMP算法 字符串匹配 模式匹配算法的实现 BF算法 数据结构 模式匹配
串的模式匹配也称为子串的定位操作,即查找子串在主串中出现的位置。设有主串S和子串T,如果在主串S中找到一个与子串T相相等的串,则返回串T的第一个字符在串S中的位置。其中,主串S又称为目标串,子串T又称为模式串。本文主要介绍两种常用的模式匹配算法,即朴素模式匹配算法——BF算法和改进算法——KMP算法。
BF算法思想是从主串 S=“s0s1…sn−1” 的第pos个字符开始与模式串 T=“t0t1…tm−1” 的第一个字符比较,如果相等则继续逐个比较后续字符;否则从主串的下一个字符开始重新与模式串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=1n−m+1 ,因此最好情况下平均比较的次数是1n−m+1∑i=1n−m+1(i−1+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趟比较。因此最坏情况下,的平均比较次数是1n−m+1∑i=1n−m+1im=m(n−m+2)2
即最坏情况下的时间复杂性是 O(n∗m) 。
假设串采用顺序存储方式存储,则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算法在BF算法的基础上有较大的改进,可在O(m+n)时间数量级上完成串的模式匹配,主要是消除了主串指针的回退,使算法效率有了很大程度的提高。
KMP算法的基本思想是在每一趟匹配过程中出现字符不等时,不需要回退主串的指针,而是利用已经得到前面“部分匹配”的结果,将模式串向右滑动若干个字符后,继续与主串中的当前字符进行比较。
假设主串S=“ababcabcacbab”,模式串T=“abcac”,KMP算法匹配过程如下左图所示。
从图中可以看出,KMP算法的匹配次数由BF算法的6趟减少为3趟。在整个KMP算法中,主串i指针没有回退。
下面讨论一般情况。假设主串 S=“s0s1…sn−1” ,模式串 T=“t0t1…tm−1” 。在模式匹配过程中,如果出现字符不匹配的情况,即当 Si≠Tj(0≤i<n,0≤j<m) 时,有
匹配过程中,当主串中的第i+1个字符与模式串中的第j+1个字符不等时,仅需将模式串向右滑动至第k+1个字符与主串中的第i+1个字符对齐,即 si 与 tj 对齐, 此时,模式串中子串 “t0t1…tk−1” 必定与主串中的子串 “si−ksi−k+1…si−1” 相等,因此匹配只需从第i+1个字符与模式串中的第k+1个字符开始比较。
如果令next[j]=k,则next[j]表示当模式串中的第j个字符与主串中的对应的字符不相等时,在模式串中需要重新与主串中与该字符进行比较的字符的位置。模式串中的next函数定义如下:
其中第一种情况,next[j]的函数是为了方便算法设计而定义的;第二种情况,如果子串中存在重叠的真子串,则next[j]的取值就是k,即模式串的最长子串的长度;第三种情况,如果模式串中不存在重叠的子串,则从子串的第一个字符开始比较。
KMP算法的模式匹配过程:如果模式串T中存在真子串 “t0t1…tk−1”=“tj−ktj−k+1…tj−1” ,当模式串T的 tj 与主串S的 si 不相等,则按照next[j]=k将模式串向右滑动,从主串中的 si 与模式串的 tk 开始比较;如果 si=tk ,则主串与模式串的指针各自增1,继续比较下一个字符;如果 si≠tk ,则按照next[next[j]]将模式串继续向右滑动,将主串中的 si 与模式串中的next[next[j]]字符进行比较;如果仍然不相等,则按照以上方法,将模式串继续向右滑动,直到next[j]=-1。这时,模式串不再向右滑动,从 si+1 开始与 t0 进行比较。
利用next函数值的一个模式匹配示例如下图所示。
模式串中的next函数值的取值与主串无关,仅与模式串有关,根据模式串next函数定义,next函数值可用递推的方法得到。
设next[j]=k,表示在模式串T中存在以下关系:
一般地,在求得next[j]=k后,如果模式串中的 tj=tk ,则当主串中的 si≠tk 时,不必再将 si 与 tk 比较,而是直接与 tnext[k] 比较。因此可以将求next函数值的算法进行修正,即在求得next[j]=k后,判断 tj 是否等于 tk ,如果相等,还需继续将模式串向右滑动,使k’=next[k],判断 tj 是否等于 tk′ ,直到两者不等为止。
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;
}
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)。一般情况下,模式串的长度比主串的长度要小得多,因此对整个字符串的匹配来说,增加这点事件是值得的。
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");
}
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;
}
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算法在模式串与主串存在许多部分匹配的情况下,其优越性才会显示出来。
文章浏览阅读331次。第一部分:准备工作1 安装虚拟机2 安装centos73 安装JDK以上三步是准备工作,至此已经完成一台已安装JDK的主机第二部分:准备3台虚拟机以下所有工作最好都在root权限下操作1 克隆上面已经有一台虚拟机了,现在对master进行克隆,克隆出另外2台子机;1.1 进行克隆21.2 下一步1.3 下一步1.4 下一步1.5 根据子机需要,命名和安装路径1.6 ..._创建一个hadoop项目
文章浏览阅读1.7k次。心脏滴血漏洞HeartBleed CVE-2014-0160 是由heartbeat功能引入的,本文从深入码层面的分析该漏洞产生的原因_heartbleed代码分析
文章浏览阅读1.4k次。前言ofd是国家文档标准,其对标的文档格式是pdf。ofd文档是容器格式文件,ofd其实就是压缩包。将ofd文件后缀改为.zip,解压后可看到文件包含的内容。ofd文件分析工具下载:点我下载。ofd文件解压后,可以看到如下内容: 对于xml文件,可以用文本工具查看。但是对于印章文件(Seal.esl)、签名文件(SignedValue.dat)就无法查看其内容了。本人开发一款ofd内容查看器,..._signedvalue.dat
文章浏览阅读1.8w次,点赞29次,收藏313次。整体系统设计本设计主要是对ADC和DAC的使用,主要实现功能流程为:首先通过串口向FPGA发送控制信号,控制DAC芯片tlv5618进行DA装换,转换的数据存在ROM中,转换开始时读取ROM中数据进行读取转换。其次用按键控制adc128s052进行模数转换100次,模数转换数据存储到FIFO中,再从FIFO中读取数据通过串口输出显示在pc上。其整体系统框图如下:图1:FPGA数据采集系统框图从图中可以看出,该系统主要包括9个模块:串口接收模块、按键消抖模块、按键控制模块、ROM模块、D.._基于fpga的信息采集
文章浏览阅读2.5w次。1.背景错误信息:-- [http-nio-9904-exec-5] o.s.c.n.z.filters.post.SendErrorFilter : Error during filteringcom.netflix.zuul.exception.ZuulException: Forwarding error at org.springframework.cloud..._com.netflix.zuul.exception.zuulexception
文章浏览阅读358次。1.介绍图的相关概念 图是由顶点的有穷非空集和一个描述顶点之间关系-边(或者弧)的集合组成。通常,图中的数据元素被称为顶点,顶点间的关系用边表示,图通常用字母G表示,图的顶点通常用字母V表示,所以图可以定义为: G=(V,E)其中,V(G)是图中顶点的有穷非空集合,E(G)是V(G)中顶点的边的有穷集合1.1 无向图:图中任意两个顶点构成的边是没有方向的1.2 有向图:图中..._给定一个邻接矩阵未必能够造出一个图
文章浏览阅读321次。(十二)、WDS服务器安装通过前面的测试我们会发现,每次安装的时候需要加域光盘映像,这是一个比较麻烦的事情,试想一个上万个的公司,你天天带着一个光盘与光驱去给别人装系统,这将是一个多么痛苦的事情啊,有什么方法可以解决这个问题了?答案是肯定的,下面我们就来简单说一下。WDS服务器,它是Windows自带的一个免费的基于系统本身角色的一个功能,它主要提供一种简单、安全的通过网络快速、远程将Window..._doc server2012上通过wds+mdt无人值守部署win11系统.doc
文章浏览阅读219次。python–xlrd/xlwt/xlutilsxlrd只能读取,不能改,支持 xlsx和xls 格式xlwt只能改,不能读xlwt只能保存为.xls格式xlutils能将xlrd.Book转为xlwt.Workbook,从而得以在现有xls的基础上修改数据,并创建一个新的xls,实现修改xlrd打开文件import xlrdexcel=xlrd.open_workbook('E:/test.xlsx') 返回值为xlrd.book.Book对象,不能修改获取sheett_xlutils模块可以读xlsx吗
文章浏览阅读8.2w次,点赞267次,收藏656次。运行Selenium出现'WebDriver' object has no attribute 'find_element_by_id'或AttributeError: 'WebDriver' object has no attribute 'find_element_by_xpath'等定位元素代码错误,是因为selenium更新到了新的版本,以前的一些语法经过改动。..............._unresolved attribute reference 'find_element_by_id' for class 'webdriver
文章浏览阅读198次。一:模态窗口//父页面JSwindow.showModalDialog(ifrmehref, window, 'dialogWidth:550px;dialogHeight:150px;help:no;resizable:no;status:no');//子页面获取父页面DOM对象//window.showModalDialog的DOM对象var v=parentWin..._jquery获取父window下的dom对象
文章浏览阅读1.7w次,点赞15次,收藏129次。算法(algorithm)是解决一系列问题的清晰指令,也就是,能对一定规范的输入,在有限的时间内获得所要求的输出。 简单来说,算法就是解决一个问题的具体方法和步骤。算法是程序的灵 魂。二、算法的特征1.可行性 算法中执行的任何计算步骤都可以分解为基本可执行的操作步,即每个计算步都可以在有限时间里完成(也称之为有效性) 算法的每一步都要有确切的意义,不能有二义性。例如“增加x的值”,并没有说增加多少,计算机就无法执行明确的运算。 _算法
文章浏览阅读1.5k次,点赞18次,收藏26次。网络安全的标准和规范是网络安全领域的重要组成部分。它们为网络安全提供了技术依据,规定了网络安全的技术要求和操作方式,帮助我们构建安全的网络环境。下面,我们将详细介绍一些主要的网络安全标准和规范,以及它们在实际操作中的应用。_网络安全标准规范