串的匹配
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1 求子串位置的定位函数Index(S,T,pos)
子串的定位操作通常称作串的模式匹配(其中T被称为模式串),是各种串处理系统中最
重要的操作之一。
在以前借用串的其他基本操作给出了定位函数的一种算法。
根据以前算法的基本思想,采用定长顺序存储结构,可以写出不依赖于其他串操作的匹配算法,如算法1所示。
int Index(Sstring S, Sstring T,int pos){
//返回子串T在主串s中第pos个字符之后的位置。
若不存在,则函数值为0。
//其中,T非空,1≤Pos≤StrLength(S)。
i=pos;
j=1;
while(i<=S[0]&&j<=T[0]) {
if(s[i]==T[j]) { i++; j++; } //继续比较后继字符 ·
else { i=i-j+2; j=1;} //指针后退重新开始匹配
}
if(i>T[0]) return i-T[0];
else return 0;
} //Index
算法 1
在算法1的函数过程中,分别利用计数指针i和j指示主串S和模式串T中当前正待比较的字符位置。
算法的基本思想是:从主串S的第pos个字符起和模式的第一个字符比较之, 若相等,则继续逐个比较后续字符,否则从主串的下一个字符起再重新和模式的字符比较之。
依次类推,直至模式T中的每个字符依次和主串S中的一个连续的字符序列相等,则称匹配成功,函数值为和模式T中第一个字符相等的字符在主串S中的序号,否则称匹配不成功,函数值为零。
图1展示了模式T="abcac"和主串S的匹配过程(pos=1)。
算法1的匹配过程易於理解,且在某些应用场合,如文本编辑等,效率也较高,例如,在检查模式"STING"是否存在於下列主串中时,
"A STRING SEARCHING EXAMPLE CONSISTING OF SIMPLE TEXT"
上述算法中的WHILE循环次数(即进行单个字符比较的次数)为41,恰好为
(Index+T[0]-1)+4这就是说,除了主串中呈黑体的四个字符,每个字符比较了两次以外,其它字符均只和模式进行一次比较。
在这种情况下,此算法的时间复杂度为O(n+m)。
其中n 和m分别为主串和模式的长度。
然而,在有些情况下,该算法的效率却很低。
例如,当模式串为"00000001",而主串为"00000000000000000000000000000000000000000000000000001"时,由于模式中前7个字符均为"0",又,主串中前52个字符均为"0",每趟比较都在模式的最后一个字符出现不等,此时需将指针i回溯到i-6的位置上,并从模式的第一个字符开始重新比较,整个匹配过程中指针i需回溯45次,则WHILE循环次数为46* 9(index*m)。
可见,算法1在最坏情况下的时间复杂度为O(n*m)。
这种情况在只有0、1两种字符的文本串处理中经常出现,因为在主串中可能存在多个和模式串“部分匹配”的子串,因而引起指针i的多次回溯。
01串可以
用在许多应用之中。
比如,一些计算机的图形显示就是把画面表示为一个01串,一页书就
是一个几百万个0和1组成的串。
在二进位计算机上实际处理的都是01串。
一个字符的ASCII 码也可以看成是八个二进位的01串。
包括汉字存储在计算机中处理
时也是作为一个01串和其它的字符串一样看待。
因此在下一节,我们将介绍另一种较好的
模式匹配算法。
2 模式匹配的一种改进算法
这种改进算法是D.E.Knuth与V.R.Pratt和J.H.Morris同时发现的,因此人们称它
为克努特-莫里斯-普拉特操作(简称为KMP算法)。
此算法可以在O(n+m)的时间数量级上完
成串的模式匹配操作。
其改进在於: 每当一趟匹配过程中出现字符比较不等时,不需回溯i
指针,而是利续进行比较。
下面先从具体例子看起。
回顾图1中的匹配过程示例,在第三趟的匹配中,当i=7,j=5字符比较不等时,又从i=4,
j=1重新开始比较。
然后,经仔细观察可发现,在i=4和j=1;i=5和j=1以及i=6和j=1这三
次比较都是不必进行的。
因为从第三趟部分匹配的结果就可得出,主串中第4、5和6个字
符必然是…b‟、…c‟和…a‟(即模式串中第2、3和4个字符)。
因为模式中的第一个字符是
a,因此它无需再和这三个字符进行比较,而仅需将模式向右滑动三个字符的位置继续进行
i=7、j=2时的字符比较即可。
同理,在第一趟匹配中出现字符不等时,仅需将模式向右移动
二个字符的位置继续进行i=3、j=1时的字符比较。
由此,在整个匹配的过程中,i指针没有回溯。
现在讨论一般情况。
假设主串为s[1..n],模式串为p[1..m],从上例的分析可知,为了实现改进算法,需要解决下述问题:当匹配过程中产生“失配”(即s[i]!=p[j])时,模式串“向
右滑动‟‟可行的距离多远,换句话说,当主串中第i个字符与模式中第j个字符“失配‟‟
(即比较不等)时,主串中第i字符(i指针不回溯)应与模式中哪个字符再比较?
假设此时应与模式中第k(k<j)个字符继续比较,则模式中前k-1个字符的子串必须满足下列关系式,且不可能存在kk>k满足下列关系式而已经得到的“部分匹配”的结果推得下列等
式
p[1..k-1]==s[i-k+1..i-1]
而已经得到的部分结果是
p[j-k+1..j-1]==s[i-k+1..i-1]
推得以下结果
p[1..k-1]==p[j-k+1..j-1]
反之,若模式串中存在满足式的两个子串,则当匹配过程中,主串中第i个字符与模式中第j个字符比较不等时,仅需将模式向右滑动至模式中第是个字符和主串中第i个字符对齐,此时,模式中头是k-1个字符的子串p[1..k-1]必定与主串中第i个字符之前长度为k-1的子
串s[i-k+1..i-1]相等,由此,匹配仅需从模式中第i个字符与主串中第j个字符比较起继续进行。
若令next[j]=k,则next[j]表明当模式中第j个字符与主串中相应字符“失配”时,在模
式中需重新和主串中该字符进行比较的字符的位置。
由此可引出模式串的next函数的定义:
next[j]=0 当j=1时
next[j]=Max{k|1<k<j && p[1..k-1]==p[j-k+1..j-1] 当此集合不空时
next[j]=1 其它情况
由此定义可推出下列模式串的next函数值:
在求得模式的next函数之后,匹配可如下进行:假设以指针i和j分别指示主串和模式
中正待比较的字符,令i的初值为pos,j的初值为1。
若在匹配过程中s[i]==p[j],则i和j 分别增1,否则,i不变,而j退到next[j]的位置再比较,若相等,则指针各自增1,否则j 再退到下一个next值的位置,依次类推,直至下列两种可能:一种是j退到某个next值(next[next ... next[j]...])时字符比较相等,则指针各自增1继续进行匹配;另一种是j退到值为零(即模式的第一个字符“失配”),则此时需将模式继续向右滑动一个位置,即从主串的下一个字符s[i+1],起和模式重新开始匹配。
下图所示正是上述匹配过程的一个例子。
KMP算法如算法2所示,它在形式上和算法1极为相似。
不同之处仅在於:当匹配过
程中产生“失配”时,指针i不变,指针j退回到next[j]所指示的位置上重新进行比较,并
且当指针j退至零时,指针i和指针j需同时增1。
即若主串的第i个字符和模式的第1个字符不等,应从主串的第i+1个字符起重新进行匹配。
int Index_KMP( SString S,SString T,int pos){
//利用模式串T的next函数求T在主串s中第pos个字符之后的位置的
//KMP算法。
其中,T非空,1≤pos≤StrLength(S)。
i=pos;
j=1;
while(i<=S[0]&&j<=T[0]){
if(j=0||S[i]==T[j]) { ++i;++j; } //继续比较后继字符
else j=next[j]; //模式串向右移动
}
if(i>T[0]) return i-T[0];//匹配成功
else return 0;.
} //Index_KMP
算法2
KMP算法是在已知模式串的next函数值的基础上执行的,那么,如何求得模式串的next函数值呢?
从上述讨论可见,此函数值仅取决于模式串本身而和相匹配的主串无关。
分析其定义出发用递推的方法求得next函数值。
由定义得知
next[1]=0
设next[j]=k, 这表明在模式串中存在下列关系:
p[1..k-1] == p[j-k+1..j-1]
其中k为满足1<k<j的某个值,并且不可能存在kk>k满足等式。
此时next[j+1]=?可能有两种情况:
(1) 若p[k]==p[j],则表明在模式串中
p[1..k]==p[j-k+1..j]
并且不可能存在kk>k满足等式,这就是说next[j+1]==k+1,即
next[j+1]==next[j]+1
(2) 若p[k]!=p[j],则表明在模式串中
p[1..k]!=p[j-k+1..j] 此时可把求next函数值的问题看成是一个模式匹配的
问题,整个模式串既是主串又是模
式串,而当前在匹配的过程中,已有p[j-k+1]==p[1],p[j-k+1]=p[2],…,p[j-1]=p[k-1],则当p[j]!=p[k]时应将模式向右滑动至以模式中的第next[k]个字符和主串中的第j个字符相比较。
若next[k]==kk,且p[j]==p[kk],则说明在主串中第j+1个字符之前存在一个长度为kk,(即next[k])的最长子串,和模式串中从首字符起长度为kk的子串相等,即
p[1..kk]==p[j-kk+1..j] (1<kk<k<j)
这就是说 next[j+1]=kk+1即
next[j+1] == next[k]+1
同理,若p[j]!=p[kk],则将模式继续向右滑动直至将模式中第next[kk]个字符和p[j]对齐,……,依次类推,直至p[j]和模式中某个字符匹配成功或者不存在任何kk(1<kk<j)满足等式,则
next[j+1]=1
例如:图4中的模式串,已求得前6个字符的next函数值,现求next[7],因为next[6]=3,
又p[6]!=p[3],则需比较p[6]和p[1](因为next[3]=1),这相当于将子串模式向右滑动。
由于P[6]!=p[1],而且next[1]=0,所以next[7]=1,而因为p[7]=p[1],则next[8]=2。
根据上述分析所得结果,仿照KMP算法,可得到求next函数值的算法,如算法3所示。
void get_next( SString T,int &next[] ) {
//求模式串T的next函数值并存入数组next。
i=1;
next[l]=O;
j=0;
while(i<T[O]) {
if(j==0||T[i]==T[j]) { ++i;++j;next[i]=j;}
else j=next[j];
}
} //get_next
算法3
算法3的时间复杂度为O(m)。
通常,模式串的长度m比主串的长度n要小得多,因此,
对整个匹配算法来说,所增加的这点时间是值得的。
最后,要说明两点:
1) 虽然算法1的时间复杂度是O(n*m),但在一般情况下,其实际的执行时间近似于
O(n+m),因此至今仍被采用。
KMP算法仅当模式与主串之间存在许多“部分匹配”的情况
下才显得比算法1快得多。
但是KMP算法的最大特点是指示主串的指针不需回溯,整个匹配过程中,对主串仅需从头至尾扫描一遍,这对处理从外设输入的庞大文件很有效,可以边读入边匹配,而无需回头重读。
2) 前面定义的next函数在某些情况下尚有缺陷。
例如模式"aaaab"在和主串"aaabaaaab"
匹配时,当i=4,j=4时s.ch[4]!=t.ch[4],由next[j]的指示还需进行i=4、j=3,i=4、j=2,i=4、j=1 等三次比较。
实际上,因为模式中第1、2、3个字符和第4个字符都相等,因此不需要再和主串中第4个字符相比较,而可以将模式一气向右滑动4个字符的位置直接进行i=5,j=1
时的字符比较。
这就是说,若按上述定义得到next[j]=k,而模式中p[j]==p[k],则当主串中字符s[i]和p[j]比较不等时,不需要再和p[k]进行比较,而直和P[next[k]]进行比较,换句话说,此时的next[j]应和next[k]相同。
由此可得计算next函数修正值的算法如算法4所示。
此时匹配算法不变。
void get_nextval(SString T,int &nextval[] ){
//求模式串T的next函数修正值并存入数组nextval。
i=1;
nextval[1]=0;
i=0; while(i<T[0]){
if(j=0 || T[i]==T[j]) {
++i; ++j;
if(T[i])!=T[j]) nextval[i] = j;
else nextval[I] = nextval[j]; }
else j=nextval[j];
}
}
(End)
第1页第2页第3页第4页。