标签:
前面说到了字符串的模式匹配的暴力方法,同时在暴力方法的基础上做了一些改进:不让主串的匹配指针i回溯,通过发掘模式串的一些特性,不断的修改模式串的匹配指针。但是模式串的匹配指针怎么修改呢,那就得要结合其自身的一些特性,然后产生相应的修改值,记录在next[j]这个数组中。
1. 寻找前缀后缀最长公共元素长度:
对于,寻找模式串P中长度最大且相等的前缀和后缀。如果存在 = ,那么在包含pj的模式串中有最大长度为k+1的相同前缀后缀。举个例子,如果给定的模式串为“abab”,那么它的各个子串的前缀后缀的公共元素的最大长度如下表格所示:
比如对于字符串aba来说,它有长度为1的相同前缀后缀a;而对于字符串abab来说,它有长度为2的相同前缀后缀ab(相同前缀后缀的长度为k + 1,k + 1 = 2)。
2. 求next数组:
next 数组考虑的是除当前字符外的最长相同前缀后缀,为什么是除了当前字符呢?回顾前面的两个例子中的模式串“abcdex”和“abcabx”,发现当匹配指针到达某一个字符并且要用到next数组的时候,这个字符一定是匹配失败的字符,在源代码中也可看到这一点,所以这个字符无论如果都要再比较的,不能从算法上逃过去,。所以通过第1步骤求得各个前缀后缀的公共元素的最大长度后,只要稍作变形即可:将第1步骤中求得的值整体减去1(0值保持不变,因为最长相同前缀后缀不可能为负数),然后初值赋为-1(这里的-1不代表最长相同前缀后缀的长度,仅仅代表这个字符是模式串的首字符),如下表格所示:
比如对于aba来说,第3个字符a之前的字符串ab中有长度为0的相同前缀后缀,所以第3个字符a对应的next值为0;而对于abab来说,第4个字符b之前的字符串aba中有长度为1的相同前缀后缀a,所以第4个字符b对应的next值为1(相同前缀后缀的长度为k,k = 1)。
3. 根据next数组进行匹配:
说了上面的两个步骤,下面进入正题,怎么根据next数组的值,在保证i值不回溯的情况下,调整j的值进行匹配。
匹配失配,j = next [j],模式串相对于主串向右移动的位数为:j - next[j]。换言之,当模式串的后缀 跟文本串 匹配成功,但Pj 跟si匹配失败时,因为next[j] = k,相当于在不包含pj的模式串中有最大长度为k 的相同前缀后缀,即,故令j = next[j],从而让模式串右移j - next[j] 位,使得模式串的前缀
综上,KMP的next 数组相当于告诉我们:当模式串中的某个字符跟文本串中的某个字符匹配失配时,模式串下一步应该跳到哪个位置。如模式串中在j 处的字符跟文本串在i 处的字符匹配失配时,下一步用next [j] 处的字符继续跟文本串i 处的字符匹配,相当于模式串向右移动 j - next[j] 位。
接下来看一个例子,具体的解释上面的东西:
1. 寻找最长前缀后缀:
如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:
也就是说,原模式串子串对应的各个前缀后缀的公共元素的最大长度表为(下简称《最大长度表》):
2. 基于《最大长度表匹配》:
因为模式串中首尾可能会有重复的字符,故可得出下述结论:
失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值
下面,结合之前的《最大长度表》和上述结论,进行字符串的匹配。如果给定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,现在要拿模式串去跟文本串匹配,这里先直接用最大前缀后缀公共元素长度表,先不用next数据。还有一点就是失配字符的前一个字符对应的最大长度值其实就是下一次匹配时模式串的匹配指针的值,例子中用的是相对于主串的右偏移量,当然两种理解方式都是可以的。如下图所示:
1. 因为模式串中的字符A跟文本串中的字符B、B、C、空格一开始就不匹配,所以不必考虑结论,直接将模式串不断的右移一位即可,直到模式串中的字符A跟文本串的第5个字符A匹配成功:
2. 继续往后匹配,当模式串最后一个字符D跟文本串匹配时失配,显而易见,模式串需要向右移动。但向右移动多少位呢?因为此时已经匹配的字符数为6个(ABCDAB),然后根据《最大长度表》可得失配字符D的上一位字符B对应的长度值为2,所以根据之前的结论,可知需要向右移动6 - 2 = 4 位。
3. 模式串向右移动4位后,发现C处再度失配,因为此时已经匹配了2个字符(AB),且上一位字符B对应的最大长度值为0,所以向右移动:2 - 0 =2 位。
4. A与空格失配,向右移动1 位。
5. 继续比较,发现D与C 失配,故向右移动的位数为:已匹配的字符数6减去上一位字符B对应的最大长度2,即向右移动6 - 2 = 4 位。
6. 经历第5步后,发现匹配成功,过程结束。
这样整个匹配过程就结束了,即使后面再有可以匹配成功的字符串,也不会匹配了。当然如果再次调用匹配函数也是可以的。
通过上述匹配过程可以看出,问题的关键就是寻找模式串中最大长度的相同前缀和后缀,找到了模式串中每个字符之前的前缀和后缀公共部分的最大长度后,便可基于此匹配。而这个最大长度便正是next 数组要表达的含义。
标签:
原文地址:http://www.cnblogs.com/stemon/p/4349975.html