五.其他表示模式值的方法


上面那种串的模式值表示方法是最优秀的表示方法,从串的模式值我们可以得到很多信息,以下称为第一种表示方法。第二种表示方法,虽然也定义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 。


void myget_nextval(const char *T, int next[]) 
{
// 求模式串T的next函数值(第二种表示方法)并存入数组 next。
int j = 1, k = 0;
next[0] = 0;
while ( T[j] != '\0' )
{
if(T[j] == T[k])
{
next[j] = k;
++j; ++k;
}
else if(T[j] != T[0])
{
next[j] = k;
++j;
k=0;
}
else
{
next[j] = k;
++j;
k=1;
}
}//while
for(int i=0;i<j;i++)
{
cout<<next[i];
}
cout<<endl;
}// myget_nextval