您的位置:首页 > 其它

字符串匹配和 KMP 算法

2017-04-27 20:26 344 查看

字符串匹配和 KMP 算法

基本匹配方法

基本的字符串匹配,可通过简单的方式解决:

int find(char *s, char *p, int pos)
{
int i = pos;    // 待搜索字符串下标
int j = 0;      // 模式当前下标
int slen = strlen(s);   // 待搜索字符串长度
int plen = strlen(p);   // 模式长度
while (i < slen)
{
if (s[i] != p[j])
{
i = i - j + 1;  // 不匹配,回溯
j = 0;
}
else
{
i++;    // 匹配,下一个位置
j++;
if (j == plen)
{
return i - plen;
}
}
}
return -1;
}

KMP

基本匹配方法每次不匹配时,都需要从字符串下个位置开始。如果模式中有部分字符串相同,如: abcab ,那么 ab 这个可以不比较,接着从模式的 c 开始,从而减少时间复杂度。

KMP 算法对模式字符串共同前后缀进行预处理,先确定模式字符串某个字符串出现不匹配时,应该如何移动模式的下标 j。前后缀是指模式字符串的前后若干个字母,当前后缀相同时,那么只需要简单将指向前缀后一个字母进行比较即可。

si == pj
时,只需要简单地
i++, j++
进行下一次比较,问题在于两者不同时。如下,

s0 s1 .....s(i-1-k).....s(i-1) si.................. sn
p0...p(j-1-k).....p(j-1) pj.....pm
p0..........p(k-1) pk..pj...pm

si != pj
,需要确定一个 k,使得有
p0...p(k-1) = s(i-1-k)...s(i-1)
,这样,就只需要 si 与 pk 进行比较,使得 i 不需要回溯,减少时间复杂度。而确定这个 k 的方法,在 KMP 中称为 next 函数或跳转表,即 k = next[j]。

当 pj 之前的字符串不为空,那么还可以得到
s(i-1-k)...s(i-1) = p(j-1-k)...p(j-1)
,因此有两个模式子字符串相等:

p0...p(k-1) = p(j-1-k)...p(j-1)
前缀            后缀

因此可见,当 k 取得最大时,得到的加速度最大,而这个 k 与 s 无关,只与模式字符串 p 有关。

当 pj 之前的字符串为空,即 j=0 时,这时相当于 si 和 p0 比较,那么此时的 k 就不能直接取 0 了,会导致 si 再次与 p0 比较而出现死循环,需要进行 i++ ,为了使得行为与匹配时类似,使此时 k = -1 ,那么可进行
i++, j++
,与匹配时处理行为相同。

现在,总结一下 kmp 的基本流程:

i = 0, j = 0;
while i < len(s):
if j = -1 or s[i] == p[j] then
i++,j++
if j == len(p):
return i - j    // 找到了
else
j = next[j]
return -1

以及对于 next :

if j = 0 then
next[j] = -1
else
next[j] = MAX({k|k 满足 p0...p(k-1) = p(j-1-k)...p(j-1),1<=k<j)},集合不空)
or
next[j] = 0 其它情况,即从 p0 开始比较

next 跳转表

next 跳转表的理解要比对 kmp 的理解要麻烦。

先按照前面的讨论,

next[0] = -1

从模式匹配的角度来看现在的情况,p 同时成为主串和模式串,对于 next[i],有 p(i-1) == p(next[i-1]),如下

p0..................p(i-1)    pi................pn
p0..........p(next[i-1]) p(next[i-1]+1)...........pn
p0.......p(j-1)     p(j).........................pn

pi == p[ next[i-1] + 1]
, :

next[i] = next[i-1] + 1

而不等时,则要找到 k 使得:

p0...p(k-1) = p(i-1-k)...p(i-1)

然后比较 pi 与 pk。看上面 KMP 对于找 k 的过程,这个过程与上面是相同的。

总结上述过程:

next[0] = -1;
i = 1, k = 0
while i < len(p):
if k == -1 || p[i] == p[k], then
i++, k++
next[i] = k
else
k = next[k]

KMP 总结

汇总以上过程,写成函数为:

int* getNext(char *p)
{
int i, k;
int plen = strlen(p);
int *next = calloc(plen, sizeof(int));

next[0] = -1;

i = 1, k = 0;
while (i < plen)
{
if (k==-1 || p[i] == p[k])
{
i++, k++;
next[i] = k;
}
else
{
k = next[k];
}
}

return next;

}

int kmp(char *s, char *p, int pos)
{
int i = pos;
int j = 0;
int slen = strlen(s);
int plen = strlen(p);

int *next = getNext(p);

while (i < slen)
{
if (j == -1 || s[i] == p[j])
{
i++, j++;
if (j==plen)
{
free(next);
return i-plen;
}
}
else
{
j = next[j];
printf("%s\n", s);
printf("%s\n", p);
printf("%d %d\n", i, j);
printf("\n");
}
}

free(next);
next = NULL;

return -1;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: