五.其他表示模式值的方法
上面那种串的模式值表示方法是最优秀的表示方法,从串的模式值我们可以得到很多信息,以下称为第一种表示方法。第二种表示方法,虽然也定义
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
。
v
oid myget_nextval(const char *T, int next[])
{
//
求模式串
T
的
next
函数值(第二种表示方法)并存入数组
next
。
int j = 1, k = 0;
next[0] = 0;