写在前面
这篇文章针对的是C/C++/Java语言程序,所以我们的下标从零开始。
KMP算法的改进
KMP算法的改进主要体现在当模式串中下标为的字符与主串中的字符失配时,在一定条件下可以从开始匹配。而不需要从头开始匹配。所以我们需要解决的问题是,当模式串中下标为的字符与主串中的字符失配时,应使用模式串中哪一个字符与继续比较。
假设当模式串中下标为的字符与主串中的字符失配时,应使用模式串中下标为的字符与继续比较。
- 当时,应当使自增1,以与模式串的开头重新比较。
- 当时
已经得到的“部分匹配”结果为。
假设应与模式串中的下标为的字符继续比较。- 当时,模式串中前个字符的子串必须满足
对比上述二式,又因为,则有。
那么可行的(若存在)即可通过上式求得。若不存在,则令。根据上式,对应的C++例程如下。inline const unsigned int getNextIndex( const std::string &pattern, const unsigned int ¤tIndex ) const { if (currentIndex >= pattern.length()) return 0;//avoid illegal input if (currentIndex == 0) return 0; bool shouldLoopBreak = false; for (unsigned int nextIndex = currentIndex - 1; shouldLoopBreak == false; shouldLoopBreak = nextIndex == 0, shouldLoopBreak ? nextIndex : --nextIndex )//上面for语句的最后一段这么写就有点傻逼了,对吧 { for (unsigned int i = 0; i < nextIndex; i++) { //当前的nextIndex不满足 if (pattern[(currentIndex - nextIndex) + i] != pattern[i]) break; //找到了符合要求的下一个下标 if (i + 1 == nextIndex) return nextIndex; } } return 0; }
- 当时,即应与模式串的开始重新匹配,与简单算法一致。
- 当时,模式串中前个字符的子串必须满足
递推生成下标跳转表
假设存在线性表nextIndex
,其中k = nextIndex[ j ]
代表当模式串中下标为j
的元素与主串中的元素发生失配时,主串中的这一元素应与下标为k
的元素尝试匹配。该线性表称为“下标跳转表”,为优化随机访问,可以采用顺序存储结构。
特别地,若采用有符号数据类型作为下表跳转表的元素,则可以令nextIndex[0] = -1
表示指向主串中被比较元素的指针应右移一位,以与模式串中第一个元素进行比较。
根据上文的分析,我们应在的前提下进行分析,尝试递推。否则关系不存在,最终产生的跳转表为从至递增的等差数列,原因从略。显然有nextIndex[1] = 0
。
假设已知k = nextIndex[ j ]
,其中j > 0
。
- 当
(k = next[ j ]) > 0
时,那么则说明模式串中存在如下关系
- 若,则有。
此时对于有next[ j + 1 ] = k + 1
- 若,则
相当于以模式串对模式串本身进行匹配,在处发生了失配。
此时可能存在使得,若满足这一条件,说明next[ j + 1 ] = k' + 1
。可以通过对进行迭代的方法尝试找到满足的。
若不存在满足上述条件,则令next[ j + 1 ] = 0
。
- 若,则有。
- 当
(k = next[ j ]) == 0
时
(k = next[ j ]) == 0
代表当模式串中与主串中失配时,需要跳转到模式串的第一个字符与主串中的进行比较。
当模式串中与主串中的失配时,必有。此时若有,则有。因此,若,则next[ j + 1 ] = 1
,否则,next[ j + 1 ] = 0
。
据此,可以写出递推生成“下标跳转表”的KMP算法C++例程如下
inline std::vector<int> generateNextArray(
const std::string &pattern
);
inline int match(
const std::string &s, const std::string &pattern
)
{
std::vector<int> next = generateNextArray(pattern);
for (
int i = 0, j = 0; i < s.length();
)
{
if (s[i] == pattern[j])
{
if (j == pattern.length() - 1)
return (i - j);//found
else ++i, ++j;
}
else
{
j = next[j];
if (j == -1)
{
++i, ++j;
}
}
}
return -1;
}
inline std::vector<int> generateNextArray(
const std::string &pattern
)
{
std::vector<int> next(pattern.length());
next[0] = -1;
next[1] = 0;
for (
std::vector<int>::size_type i = 1;
i < (pattern.length() - 1);
++i
)
{
for (
int k = next[i];; k = next[k]
)
{
if (pattern[i] == pattern[k])
{
next[i + 1] = k + 1;
break;
}
else if (k == 0)
{
next[i + 1] = 0;
break;
}
//else continue;
}
}
return next;
}
网友评论