模式匹配(C语言)
用于串学习中的字符串匹配
从主串T中匹配与字串(模式)相同的的字符,返回主串与模式匹配的首个字符的下标。
以下介绍两种算法:
//定义串格式 0号单元存放串的长度
typedef unsigned char SString[MAXSIZE + 1];
BF算法
BF算法是运用双指针i,j;i从主串的第pos个下标开始,j从模式串的第一位开始,将模式串与主串比较,依照指针i,j一一比较,若i,j指向的单位元素相等则进行i指向主串下一位置(i++),j指向模式串下一元素(j++)。若不相等则i指针返回此次主串与模式串比较的首次元素的下一个位置(i=i-j+2),j返回模式串首位(j=1),进行再次比较,直到模式串与主串上的部分序列完全相同,则匹配完成返回此次i开始的主串位置(i-T(0)),否则返回0。
int StrIndex_BF(SString S, SString T,int pos)
{
//入口参数:主串S 模式串T 开始匹配的下标pos
//返回参数:子串T在主串S中的第pos个字符之后的位置,若不存在返回0
//限制条件:T非空 1<=pos<=Strlength(S)
int 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 (j > T[0]) return i - T[0];
else return 0;
}
在最好的情况下时间复杂的为O(n+m)
在最差的情况下时间复杂的为O(n*m)
n,m分别为主串和字符串的长度
如图:pos=1(0位置储存串长度)
BF算法中因为回溯的原因导致了算法在一定情况下不适合。
KMP算法(BF算法的优化)
在上述的BF算法中,由于回溯导致了时间复杂度增加,在BF算法的图解中可以知道第2,4,5趟匹配是无效的。
如图进行优化:
在优化的图中进行了"滑动",则不是模式串右移一位,而是根据已经与主串比较的模式串的序列的规律进行滑动(由规律减少了回溯的次数)。
规律:
在模式串中查看已经匹配的子串序列,在其字串中查找最大公共前后缀,将j指针所指的位置指向最大公共前后缀的长度+1的模式串下标。
如:上图
第一趟:j=3,已匹配的子串为ab,没有最大公共前后缀,则最大公共前后缀的长度为0+1,即下一趟开始的时候j=1。
第二趟:j=5,已经匹配的子串为abca,最大公共前后缀的长度为1+1,即下一趟开始的时候j=2。
*[最大公共前后缀]:字符串前面由字符串头部开始不到尾部的子序列,字符串不由头部开始尾部结束的子序列叫做前缀和后缀。
如 :ababa的最大公共前后缀为aba,前缀为abaab,后缀为ababa。
可知滑动的长度与主串没有关系,只跟模式串的j指针的所指的模式串下标有关,即每一个模式串的元素(下标)都对应一个相应的滑动值。
将滑动的长度即最大公共前后缀的长度存在next数组中。
优化后得代码为:
kmp算法代码
int StrIndex_KMP(SString S, SString T, int pos,int next[])
{
int 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 (j > T[0]) return i - T[0];
else return 0;
}
next数组
(最大公共前后缀可得)
next值仅取决于模式串本身,而和相匹配的主串无关,可从分析其定义出发用递推的方法求得next函数值。
知next[1]=0
设已经知道了next[j]=k
则模式串中下标1到k-1 与下标j-k+1到j-1元素一一对应
则next[j+1]=?存在两种可能
- T[j]=T[k],则next[j+1]=next[j]+1
- T[j]≠T[k],则重新(查找公共前后缀(此时可把求next函数值的问题看成是一个模式匹配的问题,整个模式串既是主串又是模式串,而当前在匹配的过程中,已有tj–k+1=t1,tj–k+2=t2,…,tj−1=tk−1)此时已经知道了T[k]≠T[j],则判断T[next[k]]=T[j]是否成立,如成立则next[j]=next[k]+1。不成立则依次类推,直到T[j]与某个字符相等,否则next[j]=1。
如图:
get_next代码:
void get_next(SString T, int *next)
{
//求模式串的next函数值并存入数组next中
int i = 1, j = 0; next[1] = 0;
while (i < 9)
{
if (j == 0 || T[i] == T[j]) { ++i; ++j; next[i] = j; }
else j = next[j];
}//get _next;
}
算法复杂度:O(m)
nextval数组
next函数值的优化
如“aaaab”在和主串“aaabaaaab”匹配时,当i=4、j=4时S[4]≠T[4],由next[j]的指示还需进行i=4、j=3,i=4、j=2,i=4、j=1这3次比较。
实际上,因为模式中第1~3个字符和第4个字符都相等,因此不需要再和主串中第4个字符相比较,而可以将模式连续向右滑动4个字符的位置直接进行i=5、j=1时的字符比较。这就是说,若按上述定义得到next[j]=k,而模式中T[j]=T[k],则当主串中字符S[i]和T[j]比较不等时,不需要再和T[k]进行比较,而直接和Tnext[k]进行比较,换句话说,此时的next[j]应和next[k]相同。由此可得计算next函数修正值的算法
如图:
void get_nextval(SString T, int nextval[])
{
//求模式串T的next函数修正值并存入数组nextval中
int i = 1, nextval[1] = 0, j = 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];
}
}//get nextval;