KMP算法学习小结

来源:互联网 发布:网络与新媒体概论 编辑:程序博客网 时间:2024/06/05 04:40

晕,弄了一上午总算是搞懂了kmp的原理了,代码虽然短,但要理解好真心是蛋疼啊!
其实网上的那些什么“彻底弄懂kmp算法”什么的,感觉讲的好生涩,对于一个学渣来讲还真是难懂。
所以这里推荐一篇,自认为这一篇是最好懂的了。
链接:(http://kb.cnblogs.com/page/176818/)


其实,kmp的核心并不是这整一套算法,而是这套算法中的一个重要部分。毕竟还有更牛的叫“Boyer Moore”算法,这些都是单个字符串匹配的。
那就是那个next/p数组,叫啥来着?
哦。

                                    最长公共前后缀

(额,我不知道“居中”怎么弄…………………………….)
因为这个“最长公共前后缀”是后面比kmp更高级的“AC自动机”的基础之一,所以要搞懂,很重要。


接下来入正题。

先搞清楚什么事前缀,后缀。
前缀:指一串序列或字符串之类的,除了最后一位,其余前面全部所构成的子序列。
如:字符串I am a pig中,I、I 、I a、I am、………I am a pi,这些都是这个字符串的前缀。(包括空格哦!)
又如:序列123567中,1、12、123、1235、12356都是这个序列的前缀。
后缀:不需要我多说了吧?
如:123中,3、23是后缀。

最长公共前后缀,就是指在一个序列中,所有前缀与所有后缀中,相同的那一部分里,长度最长的那个。
如:1212。
前缀:1,12,121。
后缀:2,12,212。
只有12是相同的,那么最长的公共前后缀就是12,长度为2.

kmp中,那个next/p数组,就是“部分匹配值”数组,里面,p[i]就表示这个序列中,前i位的最长公共前后缀的长度。
如何求呢?
最基础的就是直接暴力从首位枚举+判断,处理这个数组的时间就是接近n2。(这我就不给代码了。)

然后想了想,如果一个序列有公共前后缀,那肯定是序列的某部分前缀和部分后缀相同嘛,那直接将这序列头和身上任何一个地方去比比就行了,时间就差不多是(n+n)了。

        i:=2;        while i<=m do //m是该序列长度        begin                j:=1;                while t[i]<>t[j] do //t是该序列                begin                        p[i]:=0;//p是上方定义。                        inc(i);                        if i>m then break;                end;                if i>m then break;                k:=0;                while t[i]=t[j] do                begin                        inc(k);                        p[i]:=k;                        inc(i);                        inc(j);                        if i>m then break;                end;        end;

不过速度倒是没测多少,下面是那些大犇的,超短,原理是啥?

        j:=0;        for i:=2 to m do        begin                while (j>0)and(t[j+1]<>t[i]) do j:=p[j];                if t[j+1]=t[i] then inc(j);                p[i]:=j;        end;

原来,p[i]又指在t[1~i]这个序列中,前p[i]个与后p[i]个是相同的,也就是公共前后缀。那如果t[p[i]+1]=t[i+1],那p[i+1]自然就等于p[i]+1;如果不相等,那p[i+1]就不等于p[i]+1了,(那我们只能尝试p[i+1]能否可以通过p[i]这种情况下里,某一子串得到。也就是看看t[p[p[i]]+1]是否等于t[i+1]了,不行就再往里。)ps:括号内的话,其实我也不是很懂- -||||
再想想吧。

这里是后来加的:
弄这个next数组,实际上就是为下一位i找前面j的位置。
例如t=acabacac这个序列中,例如我们现在i在第8位,j在3位,前7为next为[0,0,1,0,1,2,3],这个数组就是公共前后缀长度,因为t[7]=3,也就表示t[7]=t[3],同理有t[3]=t[1],所以有t[7]=t[1]。在计算下一位时,因为t[8]!=t[4],所以我们要找一个与t[7],t[3]一样的字符,也就是t[1],再判断t[2]?=t[8]。


说到主程序,也就是问题所述从主串里找出匹配串(又叫:模式串),那么就是模式串与主串的匹配过程,这我就不多说了,毕竟上面有链接,不懂去看就行了。

        j:=0;        for i:=1 to n do //n是主串长度        begin                while (j>0)and(t[j+1]<>s[i]) do j:=p[j];                if t[j+1]=s[i] then inc(j);                if j=m then                begin                        writeln(i-m+1,' ');                        j:=p[j];                end;        end;

这里主要提及一点,就是那个“j:=p[j]”是怎么来的。
通过上面链接,相比也知道当某一位失配时,该如何做了吧?就是将模式串往后移动一段位置,这段位置怎么求上面也有,这里再给出:“移动距离=已成功匹配数量-成功匹配的部分的部分匹配值”。
从模式串角度看,是它向后移动这么一段距离,但换个角度,会怎样?
从主串上来看,其实是将主串失配的那个字符重新和模式串的一个字符匹配,问题是和哪一个字符匹配?
应该看出来了吧?模式串向移动了s个位置,那这个失配字符自然是与模式串的失配位置往移动s个位置的那个字符匹配了。
如果用j表示当前匹配中模式串最后一个匹配成功的位置,s表示应该移动的距离.
那么失配后,j=j-s=j-(j-p[j])=p[j]。这个就是这么来的。

这样一想,这个主程序代码和之前求p的代码神似,再想想,之前求p的代码不就是自己和自己匹配吗?
有点抽象,再想想。
这里写图片描述

0 0
原创粉丝点击