定义:
主串:"S1S2S3.............Sn”
模式串: "P1P2P3................Pm"
针对字符串的模式匹配根本思想是在匹配过程中产生“失配”,模式串“向右滑动”的可行距离多远的问题.换句话说也就是,当匹配过程中产生“失配”时,主串中等i个字符(i指针不回溯)应与模式串中哪个字符再比较?
假设此时应与模式中第k(k < j)个字符继续比较,则模式中前k-1个字符的子串必须满足下列关系式,且不可能存在k‘ >k满足下列关系式
"p1p2p3...p k-1" = "S i-k+1 S i-k+2......S i-1" (1)
而已经得到的“部分匹配”的结果是
"p j-k+1 p j-k+2...p j-1 " = "S i-k+1 S i-k+2......S i-1" (2)
则
"p1p2p3...p k-1" = "p j-k+1 p j-k+2...p j-1 " (3)
反之,若模式串中存在满足(3)式的两个子串,则当匹配过程中,主串中第i个字符与模式中第j个字符比较不等时,仅需将模式向右滑动至模式中第k个字符和主串中第i个字符对齐,此时,模式中有k-1个字符的子串 "p1p2p3...p k-1"必定与主串中第i个字符之前长度为k-1的子串 "S i-k+1 S i-k+2......S i-1" 相等,由此匹配仅需从模式中第k个字符与主串中第i个字符比较继续进行。
若令next[j] = k,则next[j]表明当模式中第j个字符与主串中相应字符“失配”时,在模式中需重新和主串中该字符进行比较的字符的位置。由此可引出模式中的next函数的定义。
当 j =1时 next[j] = -1;
当 集合Max{k| 1 < k < j 且 "p1p2p3...p k-1" = "p j-k+1 p j-k+2...p j-1 " }不为空时,next[j] =Max
其它情况 next[j] = 0;
举例说明 按字符存储形式得到的数据
j 1 2 3 4 5 6 7 8
模式串 a b a a b c a c
next[j] -1 0 0 1 1 2 0 1
KMP算法如下, C#编写的
static int index_KMP(string s, string t, int pos)
{
//默认的退后位置
int[] next = { -1, 0, 0, 1, 1, 2, 0, 1 };
int i = pos;
int j = 0;
while (i < s.Length && j < t.Length)
{
if ((j == -1) || (s[i] == t[j]))
{
i++;
j++;
}
else
j = next[j];
}
if (j >= t.Length)
return i - t.Length;
else
return 0;
}
//测试程序如下
static void Main(string[] args)
{
string modeStr = "abaabcac";
string mainStr = "acabaabaabcacaabc";
int firstPos = 0;
Console.WriteLine(index_KMP(mainStr,modeStr,firstPos));
Console.ReadKey();
}
打印的索引为5。
KMP算法的基础是在已知模式串的next函数的基础上执行的,下面介绍如何对模式串求出Next[j]数组。此函数值取决于模式串本身而和相匹配的主串无关,我们可从分析其定义出发递推的方法求的next函数值。
由定义可知 next[0] = -1;
设next[j] =k,这表明在模式串中存在下列关系:
"p1p2p3...p k-1" = "p j-k+1 p j-k+2...p j-1 " ,其中k 为满足 1 < k < j的某个值,并且不可能存在k' > k 满足等式 (3),此时next[j+1] = ? 可能情况有两种
(1)若pk = pj,则表明在模式串中
"p1p2p3...p k" = "p j-k+1 p j-k+2...p j" (4)
并且不可能存在k' > k满足等式(4),这就是说next[j+1] = k+1; 即next[j+1] = next[j] + 1;
(2)若pk != pj ,则表明在模式串中
"p1p2p3...p k" != "p j-k+1 p j-k+2...p j" ,
此时可把求next函数值的问题看成一个模式匹配问题,整个模式串既是主串又是模式串,而当前在匹配的过程中,已有 p j-k+1 =p1, p j-k+2 = p2。。。p j-1 = p k-1 ,则当pk != pj 时应将模式向右滑动至以模式中的第Next[k]个字符和主串中第j个字符比较。若next[k] = k'时,且 pj = pk' ,则说明在主串中第j+1个字符之前存在一个长度为k'(即next[k])的最长子串,和模式串中从首字符起长度长度为k'的子串相等,即
"p1p2p3...p k’" = "p j-k’+1 p j-k‘+2...p j" (1 < k' < k < j) (5)
这就是说 next[j+1] = k' +1 ,即
next[j+1] = next[k] +1;
同样的,若pj != pk' 则将模式继续向右滑动直至将模式中第next[k']个字符串和pj 对齐......,依次类推,直至pj 和模式中某个字符串匹配成功或者不存在k'(1 < k' < j) 满足等式 (5),则
next[j +1] = 0;
根据以上思路,next函数代码如下
static void get_next(string T, int[] next)
{
int i = 0;
next[0] = -1;
int j = -1;
while(i < T.Length-1 )
{
if (j == -1 || T[i] == T[j])
{
++i;
++j;
next[i] = j;
}
else
{
j = next[j];
}
}
}
next函数的算法复杂度在于模式串的长度,模式串的长度m比主串的长度能n要小的多,因此,对整个匹配算法来说,所增加的这点时间是值得的。next函数针对有些情况尚有缺陷,比如模式串aaaab,则回溯时要遍历4个a, 对此情况可直接跳到首位遍历。可适当修改函数如下:
static void get_next(string T, int[] next)
{
int i = 0;
next[0] = -1;
int j = -1;
while(i < T.Length-1 )
{
if (j == -1 || T[i] == T[j])
{
++i;
++j;
if (T[i] != T[j])
next[i] = j;
else
next[i] = next[j];
//next[i] = j;
}
else
{
j = next[j];
}
}
}