提示:关于串与模式匹配的基本概念较为简单,可以自行学习,下面与大家直接讨论模式匹配算法。
一、简单的模式匹配算法
【代码实现】
int Index(SString S,SString T){
int i=1,j=1;
while(i<=S.length&&j<=T.length){
if(S.ch[i]==T.ch[j]){
++i; //后面记得i要减回1,在i-T.length实现
++j; //最后一下匹配成功了也要加1,用于后面判断(j>T.length)
}
else{
i=i-j+2; //i=i-(j-1)+1,因为i前进了(j-1)步,现在后退(j-1)步,然后向前1步
j=1;
}
}
if(j>T.length) return i-T.length; //(i-1)-(T.length-1)=i-T.length
else return 0;
}
【具体实例】
二、串的模式匹配算法——KMP算法
【算法思想】 (对于下面这个过程,从“前缀跟后缀相同”出发,思考为什么这样移动)
- 问题
主串(a b a b c a b c a c b a b)模式(a b c a c)
- 解答
- 对模式制作部分值PM
‘a’的前缀为空,后缀为空,两者交集为空;
‘ab’的前缀为{a},后缀为{b},两者交集为空;
‘abc’的前缀为{a,ab},后缀为{bc,c},两者交集为空;
'abca’的前缀为{a,ab,abc},后缀{bca,ca,a},两者交集为{a};
‘abcac’的前缀为{a,ab,abc,abca},后缀{bcac,cac,ac,c},两者交集为空
- 利用上述得到的部分匹配值PM完成匹配
【第一趟匹配过程】
发现a与c不匹配,前两个字符是匹配的,查表可知,最后一个匹配字符b对应的部分匹配值为0,因此:移动位数=已匹配的字符数 - 对应的部分匹配值=2-0=2,所以将子串向后移动2位。j=1+PM
【第二趟匹配过程】
发现b与c不匹配,前四个字符是匹配的,查表可知,最后一个匹配字符a对应的部分匹配值为1,因此:移动位数=已匹配的字符数 - 对应的部分匹配值=4-1=3,所以将子串向后移动3位。j=1+PM
【第三趟匹配过程】
成功
【具体实例】
三、串的模式匹配——KMP算法改进(对next数组改进)
(提示:如果上面的KMP算法还没弄明白,那么下面的基本就不用看了,应该回溯上述思想)
- 【对算法的第一次改进】
- 已知:右移位数=已匹配的字符数 - 对应的部分匹配值,即为Move=(j-1)- PM[j-1];
使用部分匹配值时,每当匹配失败,就去找它前一个元素的部分匹配值,这样使用起来有些不方便,所以将PM表右移一位,这样哪个元素匹配失败,直接看它自己的部分匹配值即可。- 可得:将上例中字符串’abcac’的PM表右移一位,就得到了next数组。其中第一位元素右移后空缺的用-1来填充,最后一位溢出了也没关系。
- 最后可以得到公式:
Move =(j-1)- next[j]
j = j-Move = next[j]+1
- 【对算法的第二次改进】
- 有时候为了让公式变得更加简洁,可以将next数组整体+1;
- 最后可以得到公式:
Move =(j-1)-(next[j]-1)=j-next[j]
j = next[j]
- 【next数组求取的具体代码】
(鄙人才疏学浅,没有很好的语言组织能力,需要读者自行将具体实例的i和j走一遍,自行领悟,谢谢)
void get_next(String T,int next[]){
int i=1,j=0;
next[1]=0;
while(i<T.length){
//这一段是重点,规律就在这里,总结为:循环找到,就在找到的编号+1;循环找不到,就直接等于1;
if(j==0||T.ch[i]==T.ch[j]){
++i,++j;
next[i]=j;
}
else j=next[j];
}
}
- 【具体实例】
- 【KMP匹配算法】
int Index(SString S,SString T,int next[]){
int i=1,j=1;
while(i<=S.length&&j<=T.length){
if(j==0||S.ch[i]==T.ch[j]){ //相同的话就一直匹配
++i;
++j;
}
else{ //不同的话就回溯,往往这里进行一次,上面S.ch[i]==T.ch[j]就成立了
j=next[j];
}
}
if(j>T.length) return i-T.length; //(i-1)-(T.length-1)=i-T.length
else return 0;
}
- 【算法分析】
- 尽管普通模式匹配时间复杂度是O(mn),KMP算法的时间复杂度是O(m+n),但是一般情况下,普通模式匹配的实际执行时间近似为O(m+n),因此至今仍被使用。
- KMP算法仅在主串与子串有很多“部分匹配”时才显得比普通算法快得多,其主要优点是主串不回溯。
四、串的模式匹配——KMP算法改进(对nextval数组改进)
(对nextval数组,可以在《王道考研》P118了解)
- nextval数组求取
void get_next(String T,int next[]){
int i=1,j=0;
nextval[1]=0;
while(i<T.length){
//这一段是重点,规律就在这里,总结为:循环找到,就在找到的编号+1;循环找不到,就直接等于1;
if(j==0||T.ch[i]==T.ch[j]){
++i,++j;
if(T.ch[i]!=T.ch[j]) nextval[i]=j; //与next数组的求取,区别就是对nextval[i]=j加上了条件判断
else nextval[i]=nextval[j];
}
else j=nextval[j];
}
}
- 具体实例——使用nextval数组的KMP算法
(上面使用next数组的KMP算法中,把next数组名字改成nextval数组就可以了)