目录
下一篇:树的基本概念
朴素的模式匹配算法指针每次需要前进m步再后退m-1。。。
也就是:当某些子串与模式串能部分匹配时,主串的扫描指针i经常回溯,导致时间开销增加
一、KMP思路:
主串指针不回溯,只有模式串指针回溯
匹配失败时如何回溯?
从主串从左往右扫描,若子串的指针j与主串的指针i匹配则i++,j++
若不匹配则让i++而j指向1
以下的情况j指向2
汇总:
简单来说就是要为模式串设置一个数组next告诉指针j当匹配失败时回到哪个下标才最省时
如果j=k时才发现匹配,说明1~k-1都匹配成功
需要将模式串映射为一个数组
例如google
二、代码实现:
int Index_KMP(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]){//如果匹配或j刚置为0则往后
i++;
j++;
}
else
j=next[j];//模式串向右移动
}
if(j>T.length)//匹配成功
return i-T.length;
else
return 0;
}
三、求next数组
串的前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含第一个字符的子串
例子:
a | b | a | b | a | b | ? i的位置 | ? | ? | ? | ? | ? |
|
| a | b | a | b | a j的位置 | b | c | d | e | f |
这里的模式串匹配到c时发生了不匹配
我们取该不匹配位置前的串的后缀和串的前缀进行对比,找到前后缀相同的最大长度,最后再+1就是next[j]的值
这里就是取ababab的前缀和后缀进行对比
(说明:这里不难看出串的后缀再多取一个就是babab显然与串前缀多取一个的ababa不同,所以最长为4则next[7]=5)
特别的:当第一个就匹配失败时就应该从模式串的头开始匹配
也就是next[1]=0
一般来说模式串的第一个和第二个字符也就是next[1],next[2]无脑填0和1就行了
另一种描述
四、求next数组练习
练习1:
序号j | 1 | 2 | 3 | 4 | 5 | 6 |
模式串 | a | b | a | b | a | a |
next[j] | 无脑0 | 无脑1 | 1 | 2 | 3 | 4 |
被比较的串 | 无 | a | ab | aba | abab | ababa |
练习2:
序号j | 1 | 2 | 3 | 4 | 5 |
模式串 | a | a | a | a | b |
next[j] | 0 | 1 | 2 | 3 | 4 |
五、计算next数组代码
void get_next(SString T,int next[]){
int i=1,j=0;
next[1]=0;
while(i<T.length){
if(j==0||T.ch[i]==T.ch[j]){
i++;
j++;
//若pi=pj,则next[j+1]=next[j]+1
next[i]=j;
}
else{
//否则j=next[j],循环遍历
j=next[j];
}
}
}
六、KMP优化
1、方法
将next数组优化为nextval数组
序号j | 1 | 2 | 3 | 4 | 5 | 6 |
模式串 | g | o | o | g | l | e |
next[j] | 0 | 1 | 1 | 1 | 2 | 1 |
nextval | 0 | 1 | 1 | 0 | 2 | 1 |
在该模式串的next数组中会发现当j=4时发生匹配失败时,会跳转到1但是4和1都是g,就平白无故多了一次比较
所以不妨直接将4设为1
为了更直观,我们用一个特殊的模式串
序号j | 1 | 2 | 3 | 4 | 5 |
模式串 | a | a | a | a | b |
next[j] | 0 | 1 | 2 | 3 | 4 |
nextval[j] | 0 | 0 | 0 | 0 | 4 |
一种优化方法:从左向右检测,若右边的字符与左边的字符相同则把左边的nextval值赋给右边
2、求nextval代码
//nextval数组求法
int nextval[T.length+1];
for(int j=2;j<T.length;j++){
if(T.ch[next[j]]=T.ch[j])
nextval[j]=nextval[next[j]];//因为前面一定先被赋值,所以nextval[next[j]]一定是有值的
else
nextval[j]=next[j];
}
七、小结