滑动模式串
KMP算法对于字符串匹配的改进在于当出现“失配”(即主串第i个字符和模式串第j个字符不同)的情况,并不需要回溯i指针,而是通过滑动模式串使得模式串之前从头开始的子串得到部分匹配。例如
a b c a b c a b c
a b c a b d
第6个字符失配,所以滑动模式串如下
a b c a b c a b c
a b c a b d
其中第4个字符和第5个字符得到部分匹配,接着继续进行第6个字符的匹配。
描述过程
令主串为模式串为,当si和pj失配时,i指针不进行回溯,对j指针进行回溯(即滑动模式串)找到模式串第k个字符再与si进行匹配,而模式串从1到k-1的子串要与主串从i-k+1到i-1的子串要匹配,即,既然满足这个要求,相当于模式串从1到k-1的子串要和模式串j-k+1到j-1的子串要匹配,即。
next数组
我们用next数组来记录滑动模式串的长度,即当si和pj失配时,我们要找到j之前的一个字符再与si进行匹配,next数组记录的就是找到的那个字符在模式串数组的下标,这里我们没有利用第一个元素(next[0])方便分析。
j | 1 2 3 4 5 6 7 8 |
模式串 | a b a a b c a c |
next[j] | 0 1 1 2 2 3 1 2 |
利用next数组进行匹配
我们在已经构建了next数组的情况下,若出现si和pj失配的情况,则将j指针滑动到next[j]的位置,就总会有第next[next[next...[j]]]与si匹配,然后i和j同时加一进行下一个字符的匹配,否则最后j滑动为0,i和j加一重新进行匹配。
int Index_KMP(char* s, char* p,int* next)
{
int i = 0, j = 0;
while(i <= slength && j <= plength)\\设有主串s,模式串p的长度slength和plength
{
if(j == 0 || s[i] == p[j])i++, j++;
else j = next[j];
}
if(j > plength)return i - plength;\\匹配成功就返回主串成功匹配的第一个位置
else return 0;
}
构建next数组
构建next数组的过程就相当于把模式串也当作主串,和自己进行匹配,要得到next[i]=k,使得
,k要尽可能大。有两种情况。
第一种情况为pi=pj,则next[j+1]=k+1,因为存在。
第二种情况为pi和pj失配,则需要滑动模式串找到第一个和pi匹配的k,即,然后next[i+1]=k+1,假如k=0,则没有得到部分匹配,则只能重新匹配,第i个字符要和第一个字符开始匹配,即next[i]=1。
void get_next(char* p, int* next)
{
int i = 1, j = 0;
next[1] = 0;
while(i < slength)
{
if(j == 0 || p[i] == p[j])p[++i] = p[++j];
else j = next[j];
}
}
优化
当模式串中有连续的字符出现时,例如模式串“aaaab”和主串“aaabaaaab”进行匹配,在第四个字符失配时,按照next数组模式串向右滑动,但是滑动后字符是一样的,都会失配,所以我们需要将模式串一口气滑动到底就能节约时间,所以对next数组进行改进,假如第next[i]=k个字符和第i个字符是一样的,只需将next[i]=next[k],即
p | a a a a b |
i | 1 2 3 4 5 |
next[i] | 0 1 2 3 4 |
nextval[i] | 0 0 0 0 4 |
void get_nextval(char* p, int* nextval)
{
int i = 1, j = 0;
next[1] = 0;
while(i < slength)
{
if(j == 0 || p[i] == p[j])
{
i++,j++;
if(p[i] != p[j])nextval[i] = j;
else nextval[i] = nextval[j];
}
else j = next[j];
}
}