五.其他表示模式值的方法
上面那种串的模式值表示方法是最优秀的表示方法,从串的模式值我们可以得到很多信息,以下称为第一种表示方法。第二种表示方法,虽然也定义next[0]= -1,但后面绝不会出现-1,除了next[0],其他模式值next[j]=k(0≤k<j)的意义可以简单看成是:下标为j的字符的前面最多k个字符与开始的k个字符相同,这里并不要求T[j] != T[k]。其实next[0]也可以定义为0(后面给出的求串的模式值的函数和串的模式匹配的函数,是next[0]=0的),这样,next[j]=k(0≤k<j)的意义都可以简单看成是:下标为j的字符的前面最多k个字符与开始的k个字符相同。第三种表示方法是第一种表示方法的变形,即按第一种方法得到的模式值,每个值分别加1,就得到第三种表示方法。第三种表示方法,我是从论坛上看到的,没看到详细解释,我估计是为那些这样的编程语言准备的:数组的下标从1开始而不是0。
下面给出几种方法的例子:
表一。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
T | a | b | a | b | c | a | a | b | c |
(1) next | -1 | 0 | -1 | 0 | 2 | -1 | 1 | 0 | 2 |
(2) next | -1 | 0 | 0 | 1 | 2 | 0 | 1 | 1 | 2 |
(3) next | 0 | 1 | 0 | 1 | 3 | 0 | 2 | 1 | 3 |
第三种表示方法 , 在我看来,意义不是那么明了,不再讨论。
表二。
下标 | 0 | 1 | 2 | 3 | 4 |
T | a | b | c | A | c |
(1)next | -1 | 0 | 0 | -1 | 1 |
(2)next | -1 | 0 | 0 | 0 | 1 |
表三。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
T | a | d | C | a | d | C | a | d |
(1)next | -1 | 0 | 0 | -1 | 0 | 0 | -1 | 0 |
(2)next | -1 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
对比 串的模式值第一种表示方法和第二种表示方法,看表一:
第一种表示方法 next[2]= -1, 表示 T[2]=T[0] ,且 T[2-1] !=T[0]
第二种表示方法 next[2]= 0, 表示 T[2-1] !=T[0], 但并不管 T[0] 和 T[2] 相不相等。
第一种表示方法 next[3]= 0, 表示虽然 T[2]=T[0] ,但 T[1] ==T[3]
第二种表示方法 next[3]= 1, 表示 T[2] =T[0], 他并不管 T[1] 和 T[3] 相不相等。
第一种表示方法 next[5]= -1, 表示 T[5]=T[0] ,且 T[4] !=T[0] , T[3]T[4] !=T[0]T[1] , T[2]T[3]T[4] !=T[0]T[1]T[2]
第二种表示方法 next[5]= 0, 表示 T[4] !=T[0] , T[3]T[4] !=T[0]T[1] , T[2]T[3]T[4] !=T[0]T[1]T[2] ,但并不管 T[0] 和 T[5] 相不相等。换句话说:就算 T[5]==’x’, 或 T[5]==’y’,T[5]==’9’, 也有 next[5]= 0 。
从这里我们可以看到:串的模式值第一种表示方法能表示更多的信息,第二种表示方法更单纯,不容易搞错。当然,用第一种表示方法写出的模式匹配函数效率更高。比如说,在串 S= “ adCadCBdadCadCad 9876543 ”中匹配串 T= “ adCadCad ” , 用第一种表示方法写出的模式匹配函数 , 当比较到 S[6] != T[6] 时,取 next[6]= -1 (表三) , 它可以表示这样许多信息: S[3]S[4]S[5]==T[3]T[4]T[5]==T[0]T[1]T[2] ,而 S[6] != T[6] , T[6]==T[3]==T[0] ,所以 S[6] != T[0], 接下来比较 S[7] 和 T[0] 吧。如果用第二种表示方法写出的模式匹配函数 , 当比较到 S[6] != T[6] 时,取 next[6]= 3 (表三) , 它只能表示: S[3]S[4]S[5]== T[3]T[4]T[5]==T[0]T[1]T[2] ,但不能确定 T[6] 与 T[3] 相不相等,所以,接下来比较 S[6] 和 T[3]; 又不相等,取 next[3]= 0 ,它表示 S[3]S[4]S[5]== T[0]T[1]T[2] ,但不会确定 T[3] 与 T[0] 相不相等,即 S[6] 和 T[0] 相不相等,所以接下来比较 S[6] 和 T[0] ,确定它们不相等,然后才会比较 S[7] 和 T[0] 。是不是比用第一种表示方法写出的模式匹配函数多绕了几个弯。
为什么,在讲明第一种表示方法后,还要讲没有第一种表示方法好的第二种表示方法?原因是:最开始,我看严蔚敏的一个讲座,她给出的模式值表示方法是我这里的第二种表示方法,如图:
她说:“ next 函数值的含义是:当出现 S[i] !=T[j] 时,下一次的比较应该在 S[i] 和 T[next[j]] 之间进行。”虽简洁,但不明了,反复几遍也没明白为什么。而她给出的算法求出的模式值是我这里说的第一种表示方法 next 值,就是前面的 get_nextval() 函数。匹配算法也是有瑕疵的。于是我在这里发帖说她错了:
现在看来,她没有错,不过有张冠李戴之嫌。我不知道,是否有人第一次学到这里,不参考其他资料和明白人讲解的情况下,就能搞懂这个算法(我的意思是不仅是算法的大致思想,而是为什么定义和例子中 next[j]=k(0 ≤ k<j) ,而算法中 next[j]=k(-1 ≤ k<j) )。凭良心说:光看这个讲座,我就对这个教受十分敬佩,不仅讲课讲得好,声音悦耳,而且这门课讲得层次分明,恰到好处。在KMP这个问题上出了点小差错,可能是编书的时候,在这本书上抄下了例子,在那本书上抄下了算法,结果不怎么对得上号。因为我没找到原书,而据有的网友说,书上已不是这样,也许吧。说起来,教授们研究的问题比这个高深不知多少倍,哪有时间推演这个小算法呢。总之,瑕不掩玉。
书归正传,下面给出我写的求 第二种表示方法表示的模式值的函数 , 为了从 S 的任何位置开始匹配 T ,“当出现 S[i] !=T[j] 时,下一次的比较应该在 S[i] 和 T[next[j]] 之间进行。” 定义 next[0]=0 。