KMP算法的简单梳理
KMP算法是字符串匹配问题的一种效率解法,有很多初学者对KMP的实现一头雾水,以下是我对KMP算法及其改进的简单整理。
基础KMP
KMP算法的核心在于利用匹配失败后的信息,尽量减少模式串与主串的匹配次数而达到快速匹配的目的。
其时间复杂度为O(m+n)O(m+n)O(m+n)
假定:主串为S1S2...SnS_1S_2...S_nS1S2...Sn,模式串为P1P2...PmP_1P_2...P_mP1P2...Pm。(模式串为需要匹配的字符串,是主串的子串)
每个元素拥有一个独立的Next()函数用于判断遇到不同的元素该从哪里开始匹配。本质是用于寻找模式串的最长相同前缀和后缀。
举个例子,模式串 str=abaabcac,假设主串有一段为abab,前三个字符aba与模式串前三个完美匹配,但是第四个字符出现了差错,Next数组中存储了模式串在匹配失败位置前的公共前缀信息,前三个字符之中,str[3]与str[1]相同,构成了公共前缀,因此第四个位置出错之时,我们可以跳过str[1],直接将第四个位置的字符与str[2]的字符进行匹配。
比较时只移动模式串,找到当前已匹配的串与模式串的最长前缀
以下是Next数组的计算公式:
Next[i]={0(j=1时)Max(k ∣ 1<k<j and p1p2...pk−1=pj−k+1...pj−11(其他情况)Next[ i ]=\begin{cases} 0 (j=1时)\\Max(k\ |\ 1<k<j \ and\ p_1p_2...p_{k-1}=p_{j-k+1}...p_{j-1}\\1 (其他情况)\end{cases}Next[i]=⎩⎨⎧0(j=1时)Max(k ∣ 1<k<j and p1p2...pk−1=pj−k+1...pj−11(其他情况)
模式串为abaabcac时得出的Next数组:
j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
String[j] | a | b | a | a | b | c | a | c |
Next(j) | 0 | 1 | 1 | 2 | 2 | 3 | 1 | 2 |
Next数组计算代码:
void Make_next(String p, int *pNext)
{
int i, k;
i = 1;
k = 0;
int j = 1;
pNext[1] = 0;
while(i < p.size())
{
if(k == 0 || p[i] == p[k])
{
j++;
k++;
pNext[j] = k;
}
else k = pNext[k];
}
}
改进后的KMP
基础的KMP算法并没有运用到匹配失败的信息,即P[J](模式串匹配失败位置)的信息,在使用Next[j]进行回溯时进行了多余的运算。
就比如说,主串为abacababc,模式串为abab,在进行匹配时,在第四个位置abac与abab匹配失败,按照原有的KMP算法,主串的c将与第二个字符重新进行匹配,但是显而易见,在匹配第四个位置时字符不为 b,其对第二个字符 b 的匹配也必然失败。
所以说KMP的改进就是增加了对模式串匹配失败位置的信息处理。
因此,在需要子串指针回溯时,进行当前位置元素与回溯之后的位置元素比较,如果相等,子串的指针继续回溯。
以下是改进的 Next 数组:Nextval 数组的推导公式:
Nextval[i]={next[i] (pi != pnext[i])nextval[next[i]] (pi == pnext[i])Nextval[ i ]=\begin{cases} next[i] \ (p_i\ !=\ p_{next[i]})\\nextval[next[i]]\ (p_i\ ==\ p_{next[i]})\end{cases}Nextval[i]={next[i] (pi != pnext[i])nextval[next[i]] (pi == pnext[i])
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数组的代码:
void get_nextval(string p, int* nextval)
{
int i = 1, j = 0;
nextval[1] = 0;
while(i < p[0])
{
if(j == 0 || p[i] == p[j])
{
i++;
j++;
if(p[i] != p[j])
{
nextval[i] = j;
}
else
{
nextval[i] = nextval[j];
}
}
else j = nextval[j];
}
}