KMP模式匹配算法中next和nextval数组的快速求解

在这里,不再对大名鼎鼎的KMP算法做过多赘述,如想了解可参考http://blog.csdn.net/v_JULY_v/article/details/6111565,写的很好!

首先,阐明一下这篇文章的目的:快速求解模式串的next以及nextval数组。当然这里面的模式串都比较短,在笔试或者面试的时候很有可能遇到,因此,如何快速求得是至关重要的!
废话不多说,我们直接切入主题,即关于next数组是如何求解的。而nextval数组是对next数组的改进,有这么个循序渐进的过程,所以先从next数组说起。我们都知道:next数组是表征模式串p自身的匹配程度的,所以我们先从一个串的前缀和后缀表达式说起。

前缀

后缀

最大长度

a

0

aa

a

a

1

ab

a

b

0

aba

a,ab

a,ba

1

abab

a,ab,aba

b,ab,bab

2

ababa

a,ab,aba,abab

a,ba,aba,baba

3

ababab

a,ab,aba,abab,ababa

b,ab,bab,abab,babab

4

abababa

a,ab,aba,abab,ababa,ababab

a,ba,aba,baba,ababa,bababa

5

aaaaaab

a,aa,aaa,aaaa,aaaaa,aaaaaa

b,ab,aab,aaab,aaaab,aaaaab

0


以上表中串ababa为例:其前缀式有a,ab,aba,abab,而后缀式有a,ba,aba,baba,前缀与后缀匹配的串为:a,aba,因此串ababa的前缀与后缀的最大匹配长度为3。

好了,到这里可以直接来求next数组了!

这里,以模式串p="ababab"为例吧!

1. next[0]=-1,这个应该没有疑问的,取-1是因为字符串都是从下标0开始索引的(当然,也可以取为0,这时字符串的下标就要从1开始了);

2. next[1]=?我们知道p[1]=b,p[1]之前的子串只有"a",而字符串"a"的前缀与后缀最大匹配长度为0(上表第二行所示),因此,有next[1]=0;

3. next[2]=?从上一步的求解中可发现:欲求next[i],先找子串p[0~i-1]的前缀与后缀最大匹配长度,这也是与next数组的定义相吻合的next[k]=max{k|p[0~k-1]=p[i-k~i-1]},当然如果没有这样的k的话,就取next[k]=0,例如p[0~i-1]="ab"时;因此,next[2]=0;

知道了如何求解next数组的方法,做起来就很容易了!

a  b a b a b

-1 0 0 1 2 3

a  a a a a a b

-1 0 1 2 3 4 5

a  b a b b a b a c b a a b

-1 0 0 1 2 0 1 2 3 0 0 1 1

好了,到这里相信你已经可以独立的算出模式串的next数组了,下面,我们由next数组来推导nextval数组!

首先,对next数组做一个延伸,即多求一位next[i+1]:

a  b a b a b

-1 0 0 1 2 3 4

a  a a a a a b

-1 0 1 2 3 4 5 0

a  b a b b a b a c b a a b

-1 0 0 1 2 0 1 2 3 0 0 1 1 2

注意上文中的红色数字,它们是由整个模式串的前缀后缀的最大匹配长度得来的。

a  b  a b  a  b

-1 0  0 1  2  3  4

-1 0 -1 0 -1 0

a   a  a   a  a  a  b

-1  0  1   2  3  4  5  0

-1 -1 -1 -1 -1 -1 5

a  b  a b b  a b  a  c  b  a  a  b

-1 0  0 1 2  0 1  2  3  0  0  1  1  2

-1 0 -1 0 2 -1 0 -1 3  0 -1  1  0

蓝色字体的就是nextval数组,不知道大家有没有看出一些规律来?

首先,还是从nextval对next的改进之处说起吧!

void getnext(char p[])
{
	next[0]=-1;
	int lenp=strlen(p);
	int i=0,j=-1;
	while(i!=lenp-1)
	{
		if(j==-1||p[i]==p[j])
		{
			++i;
			++j;
			next[i]=j;
		}
		else
			j=next[j];
	}
}
void getnextval(char p[])
{
	nextval[0]=-1;
	int i=0,j=-1,lenp=strlen(p);
	while(i!=lenp-1)
	{
		if(j==-1||p[i]==p[j])
		{
			++i;
			++j;
			if(p[i]!=p[j])
				nextval[i]=j;
			else
				nextval[i]=nextval[j];
		}
		else
			j=nextval[j];
	}
}

从上面两段代码,可发现nextval对next的改进之处在于当p[i]==p[j]时,nextval[i]=nextval[j],而不再是nextval[i]=j。

注意到这一点之后,我们首先给出一个结论:当p[i]==p[j]时,必然有next[i+1]=next[i]+1(注意这里是next数组而不是nextval数组)。

来分析一下:

假定next[i]=k,则必有

k>=1:p[0~k-1]=p[i-k~i-1],j=k;若p[i]==p[j],—>p[0~k]=p[i-k~i] —>next[i+1]=k+1=next[i]+1;

k==0:p[0]!=p[i-1],j=0;若p[i]==p[j],—>p[0]=p[i] —>next[i+1]=1=next[i]+1;

好的,下面我们来见证一下如何从next数组来得到nextval数组:

若next[i+1]=next[i]+1,则nextval[i]=nextval[next[i]],0<=i<=strlen(p)-1;

对的,就是这么简单!

索引

0

1

2

3

4

5

6

7

8

9

10

11

12

13

模式串

a

b

a

b

b

a

b

a

c

b

a

a

b

 

next[]

-1

0

0

1

2

0

1

2

3

0

0

1

1

2

nextval[]

-1

0

-1

0

2

-1

0

-1

3

0

-1

1

0

 

上表中彩色部分一目了然!

至此,相信你已经可以快速完成next、nextval数组的求解了!

你可能感兴趣的:(algorithm)