首先简单的提一下BF算法(定义不加阐述):
在进行模式匹配时,i(主串) 下标的值与 j (子串)下标的值进行比较,若相等则 i 和 j 挪到下一位,若失配的话则 j 回退到 0 号下标位置,i 回退到 i - j +1的位置,循环往复
这种算法是解决模式匹配的一种直接算法,以图为例,我们可以看到,失配字符前的字符没有 a ,所以 i ,j 回退完全就是浪费时间。对于此 ,我们对此算法进行优化,就是KMP算法(定义不加阐述),KMP算法在失配时,不需要回退 i ,只需将 j 回退到“合适位置”
关键在于这个合适位置,下面我们来找找这个合适位置,方法叫做next[]数组法;我们定义一个next[]数组,在这我们需要了解前缀和后缀,因为我们next[]数组是用来保存最大前缀和最大相等后缀的值的,在next[]数组中,next[]数组中数字不仅代表最大前缀 和 后缀相等数,也代表 子串中 j 要回退的位置,这就是所谓的那个合适位置
比如:abcjkdabc,那么这个数组的最长前缀和最长后缀相同是abc。
cbcbc,最长前缀和最长后缀相同是cbc。
abcbc,最长前缀和最长后缀相同是不存在的。
我们以上图为例,很多人可能不明白下边那一行数字咋来的,在这我们进行分析:
在定义next[]数组时,我们定义 j=0时,k=-1, next[0] = -1 (含义相同),j=1时的next[1]=++k,查找假设在第二个b处失配,即第一个b与 0号下标a相比,失配时值记为 0,存在第二个b下,同理后边数字也是一样的道理
KMP算法核心:在进行模式匹配时,若匹配不成功,i 不需要回退,j 需要回退到最大前缀和最大后缀相等的值处,即 j 所指向当前的next[k]的值处,然后让 j 所指向的值与 i 值进行匹配,(便于理解可以认为让 j 指向的值与 i 指向的值对齐进行比较)若回退的值为 --1时,则说明在主串 i 位置之前的数据都失配,这时,让主串中 i++,子串中j++
以上图为例,执行KMP算法进行模式匹配
代码实现如下:
void GetNext(PString ps, int* next)
{
if (ps == NULL)
{
return;
}
int k = -1;
int i = 1;
int len = ps->curlen;
if (len == 0)
{
return;
}
next[0] = -1;
if (len == 1)
{
return;
}
next[1] = ++k;
while (i < len - 1)
{
if (k == -1 || ps->pstr[i] == ps->pstr[k])
{
next[++i] = ++k;
}
else
{
k = next[k];
}
}
}
int KMP(PString ps1, PString ps2)
{
if (ps1 == NULL || ps2 == NULL)
{
return -1;
}
int len1 = ps1->curlen;
int len2 = ps2->curlen;
int i = 0;
int j = 0;
int* next = (int*)malloc(sizeof(int)*len2);
GetNext(ps2, next);
while (i < len1 && j < len2)
{
if (j == -1 || ps1->pstr[i] == ps2->pstr[j])
{
++i;
++j;
}
else
{
j = next[j];
}
}
free(next);
if (j >= len2)
{
return i - j;
}
return -1;
}
另附BF算法: