目录
简单介绍三种方法。
1、利用串的其他操作实现模式匹配
//返回子串T在主串S中第pos个字符之后的位置
int Index(String S, String T, int pos)
{
int n,m,i;
String sub;
if(pos>0){
n = StrLength(S);
m = StrLength(T);
i = pos;
while(i <= n-m+1){
SubSting(sub,S,i,m); //取主串第i个位置长度与T相等的子串给sub
if(StrCompare(sub,T) != 0) //如果两串不相等
++i;
else
return i; //如果两串相等则返回i值
}
}
return 0; //若无子串与T相等,返回0
}
代码当中用到了StrLength、SubString、StrCompare等基本操作。
int StrLength(String S) //返回串长
{
return S[0];
}
//用Sub返回串S的第pos个字符起长度为len的子串
Status SubString(String Sub, String S, int pos, int len)
{
int i;
if(pos<1 || pos>S[0] || len<0 || len>S[0]-pos+1)
return ERROR;
for(i=1; i<len; i++)
Sub[i] = S[pos+i-1];
Sub[0] = len;
return OK;
}
//串的比较:若S>T,返回值>0;S<T,返回值<0;S=T,返回值=0。
int StrCompare(String S,String T)
{
int i;
for(i=1;i<=S[0]&&i<=T[0];i++){
if(S[i] != T[i])
return S[i]-T[i];
}
return S[0]-T[0];
}
2、朴素的模式匹配算法:不用串的其他操作,只用基本数组实现
简单来说就是对主串的每一个字符作为子串的开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做子串长度的小循环,直到匹配成功或全部遍历结束为止。
//朴素模式匹配
//返回子串T在主串S中第pos个字符之后的位置
int Index(String S, String T, int pos)
{
int i = pos; //i用于主串S中当前位置下标值
int j = 1; //j用于子串T中当前位置下标值
while(i <= S[0] && j <= T[0]) //将主串长和要匹配的子串长存放在S[0]和T[0]中
{
if(S[i] == T[j]){ //两字母相等则继续
++i;
++j;
}
else{ //指针后退重新开始匹配
i = i-j+2; //i后退到上次匹配首位的下一位
//i = i-(j-1)+1 (i的新位置 = 旧位置+1)
j = 1; //j后退到子串T的首位
}
}
if(j > T[0])
return i-T[0];
else
return 0;
}
时间复杂度:(n为主串长,m为子串长)
最好情况:一开始就匹配成功,O(1)
最坏情况:每次不成功的匹配都发生在子串T的最后一个字符,O((n-m+1)*m)
平均情况:(n+m)/2次查找,O(n+m)
3、KMP
朴素模式匹配:主串S的i值不断回溯来完成。KMP算法就是为了让这没必要的回溯不发生。
KMP算法中:i值不可以变小。那么既然i不能回溯,要考虑的变化就是j值了。子串T的首字符与自身后面的字符之间的比较,如果发现有相等字符,j值的变化就会不同。即:j值的变化与主串S没什么关系,关键在于T串的结构中是否有重复的问题。
3.1 KMP算法
我们把T串各个位置的j值的变化定义为一个数组next:
如果前后缀一个字符相等,k值为2;两个字符相等k为3;那个相等则k为n+1。没有相等字符属于其它情况,next[j]=1。
next[j]是一个部分匹配表:有时候字符串头部和尾部会重复,比如"abcdab"中有两个"ab",那么它的部分匹配值就是2,实际上就是"ab"的长度。搜索词移动时,第一个"ab"向后移动4位(字符串长度 - 部分匹配值)就可以来到第二个"ab"的位置。
失配时,T相对于主串S向右移动了j-next[j]位。当某个字符失配时,该字符对应的next值会告诉你下一步匹配中,T应该跳到那个位置。如果next[j]=0/1,则跳到T的开头字符;如果next[j]=k且k>1,代表下次匹配跳到j之前的某个字符,且具体跳过了k个字符。
//KMP算法
//通过计算返回子串T的next数组
void get_next(String T, int *next)
{
int i=1,j=0;
next[1]=0;
while(i<T[0])
{
if(j==0 || T[i]==T[j]) //若字符相同
{ //T[i]表示后缀的单个字符,T[j]表示前缀的单个字符
++i;
++j;
next[i] = j;
}
else
j = next[j]; //若字符不相同,则j值回溯
}
}
//返回子串T在主串S中第pos个字符之后的位置
int Index_KMP(String S, string T, int pos)
{
int i = pos; //i用于主串S中当前位置下标值
int j = 1; //j用于子串T中当前位置下标值
int next[255]; //定义next数组
get_next(T, next); //对子串T作分析,得到next数组
while(i<=S[0] && j<=T[0])
{
if(j==0 || S[i]==T[j]){ //两字母相等则继续
++i;
++j;
}
else //指针后退开始重新匹配
j = next[j]; //j退回合适的位置,i值不变
//移动位数 = 已匹配的字符数 - 对应的部分匹配值
}
if(j>T[0])
return i-T[0];
else
return 0;
}
算法时间复杂度为O(n+m)。
KMP算法仅在子串与主串之间存在许多“部分匹配”的情况下才能体现出优势,否则和朴素的模式匹配没有很大差异。
3.2 KMP算法改进
//KMP算法改进
//求模式串T的next函数修正值并存入数组nextval
void get_nextval(String T, int *nextval)
{
int i=1,j=0;
nextval[1]=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];
}
}
}
在计算出next值的同事,如果a位字符与它next值指向的b位字符相等,则该a位字符的nextval就指向b位的nextval值;若不相等,则该a位的nextval值就是它自己a位的next值。