转载请注明来自souldak,微博:@evagle
这回思路要更清晰了。 目标串T:ababzabcd 模式串P:ababx 关键是在z和x不相等的时候,P应该往前移动多少个。或者说,哪哪个字符和当前的z比较。 很明显,x之前的字符串abab肯定和z之前的都相同,那我们只需要研究abab就行了。 先放好abab,然后用abab从后往前比较,看看最多能往前移动多少能够匹配,例如: 1. abab abab 2. abab abab 3. abab abab 显然就只有第二个情况是匹配的,现在把后面的z和x分别加上就是 ababzabcd ababx 很明显,这样保证了z之前的ab和下面的模式串相等,只要比较z和z下面的字符是否相等,(这个例子是a)不想等。 这里我们直接比较z和P的第3个字符,按照普通匹配,我们需要做的是将P往后移动一个字符,然后从头开始匹配,所以这节省了时间。 那接下来就是比较不相等的时候,P应该往前移动多少,T中不相等的字符应该和P中哪个字符比较的问题了。 对于P=p0p1p2…pn-1, 令 next[0] = -1; next[j]= max{k, p0p1…pk-1==pj-k…pj-1} j>1 像上面的abab, next[len] 就是从最开始数k个,从最后面倒着数k个,使得这两个子串相等,next[len]就是最大的这样的k。 例如
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
p | a | b | c | a | b | c | a | b | b | a | c |
next | -1 | 0 | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 0 | 1 |
例如next[6] 其实就是abcabc从头开始,和从最后面倒着数k个使得连个子串相等的最大k,显然从头数3个是abc,最后3个也是abc,所以就是next就是3,而实际含义是,如果第p[6]=a 和T中的字符比较不相等时, 例如T是abcabcxdjfk abcabcxdjfk abcabca 因为x!=a, 所以将P的最开头3个和x之前的对齐,如下 abc
abcxdjfk:结尾开始数3个
abcabca:开头开始数3个 因为next[6]=3表示从前数3个,T 串中是从后面开始算的,而P中是从头开始算的,这就是next的含义。
接下来就是如何快速求next数组。 我们自己来人眼来解决就是,从头数和从后面数,看看字符串相等的,得到最大的,这个是定义。 但是其实next可以更具前面的来得到, 两种情况, next[j+1]可以从next[j]得到 next[j]=k表示p0p1…pk-1==pj-k…pj-1,那如果pk==pj,那么next[j+1]=next[j]+1=k+1,因为p0p1…pk==pj-k-1…pj 另外的情况是pk!=pj, 如下:高亮部分是相等的 X: p0p1..
.pj-k…pj-1
pj Y:
p0p1…pk-1
pk pj!=pk, 所以需要需要Y 网后移,以便找到和pj相等的字符。 所以需要移动到q=next[k], 然后因为next[k]=q,说明
p0…pq-1 = pk-q…pk-1 = pj-q…pj-1 X: p0p1…pj-k…pj-1 pj Y: p0…pq-1 pq 然后再次比较pj与pq,如果相等next[j]=next[q]+1,否则重复上面的过程。直到p[0], 例如最上面表格里的例子,计算next5,6,7,8都是直接加1就好了,但是计算next[9]时, 第一次尝试:
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
p | a | b | c | a | b | c | a | b | b | a | c |
p‘ | a | b | c | a | b | c |
next[8]=5, 而p[5]=c != p[8]=b, 下一次尝试,需要将p’ 往后移动,移到next[5]=2与8对齐, 即如下:
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
p | a | b | c | a | b | c | a | b | b | a | c |
p‘ | a | b | c |
发现p[8]!=p[2] 所以还要往后移对齐,next[2]=0 即p[0]与p[8]对比,
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
p | a | b | c | a | b | c | a | b | b | a | c |
p‘ | a |
发现p[8]!=p[0] 这个时候next[0]=-1了,已经移动完毕了也没有找到,所以next[9]=0, 就是说如果比对到p[9]!=t[x]的话,就只能将p[0]和tx[0]比较了,直接往前移动了9格!
下面是计算next数组的函数。 void compute_next(int * next, chat const*p, int len){ int j=0; int q = next[0]=-1; –len; while(j<len){ if(q==-1 || p[q]==p[j]){ next[++j]=++q; //这个是相等的情况( p[q]==p[j]),或者前面已经无法匹配,只能全部往前移动的情况(q==-1) }else q=next[q];// 这个是不想等的情况,那么将p‘ 往前移动到next[q], p[next[q]]和p[j]比较,详细见上面的分析, } }
虽然KMP每本算法书都会有,但是其效率并不是很好,它的平均性能和直接匹配相差无几。反而是sundy算法(BM算法),从后往前匹配的效果要更好。