KMP的科普文章: 为什么KMP提高了效率

2021.09.26 补充:原文在 2011年10月31日发表在之前的 Blog 上,本文是基于 Internet Archive 上面的记录手动迁移过来的内容。

以下为正文:

本文原创,转载请注明出处: http://atarss.com/blog/?p=193

计科无能者无视第一段,着急写作业的建议不用看第三部分,直接用第二段的方法。

KMP的实质是自动机匹配的优化,即不需要构造自动机,而通过模式字符串的自我匹配来简化过程,使预处理的时间复杂度降为O(m)。

1.字符串匹配的过程;以及为什么我们能优化算法

假设要被搜索的字符串是”bacbababaabcbab”,要搜索”ababaca”这个串。

浅绿色的表示已经被匹配的字符,我们定义正在被匹配的长字符串里面的位置是i=5,要搜索的模式串已经匹配到j=6第6个字符而且与原始的字符串不匹配。按照朴素的匹配算法,我们应该让i++,即从i=6开始匹配。但是我们已经知道i从5~9与B串的j=1~5是完全相同的。因此即使我们不知道A串的内容,只通过B串的内容以及我们已经匹配到第6个字符这两项数据,就可以知道从i的下一项数据一定是不匹配的。

同理我们知道主串在i=7时的匹配可能是成功的,因此我们可以设计算法不去匹配i=6时的情况而直接跳到i=7去匹配,而且右移2位这个数字2只是通过B串(要搜索的串)的自我匹配来完成的,也就是可以在匹配前完成计算。而这就是KMP较朴素匹配算法的优化之处。

2.如何计算当不匹配时主串要移动多少位

依旧要搜索这个串”ababaca”,我们已经知道要跳过多少个字符的字符数量是与要搜索的字符串(A串)无关的,实质是要搜索的串(B串)的自我匹配。而这个信息可以被预先计算出来并被存储起来。

每次偏移多少位显然与匹配到B串第几个字符时开始不匹配的j位置有关,因此我们定义一个数组来存储这个偏移量的信息,定义为D[1..t],t为B串的长度,下标从1开始。

※我们前提假设,每个字符只能读取一次,真正的字符指针不能后退。

实质即为,假设当匹配到j=n时不匹配,即j从1~(n-1)都是匹配的,那么偏移量就是字符串从后向前自我匹配时到达前面之前所能达到的最大的位置。举例:

按找从下往上看的顺序就能找到偏移量,显然最长的匹配的偏移量为2。按照这种方法就能找到所有的偏移数组的值。

若j=1时不匹配,毫无悬念,偏移量为1,;
若j=2时不匹配,则右移一位也没有意义,直接右移两位,偏移量为2;
若j=3时不匹配,i=3与j=1时相同,因此偏移量为3-1=2;

若j=4时不匹配,同理得偏移量为2;
j=5时上面已经分析过,偏移量为5-3=2;
j=6时,同理,偏移量为6;
j=7时,偏移量为7-1=6;

在计算的过程中,我们知道,真正找到的并不是偏移量,而是最长真字串的长度。我们成这个关于j的数组(函数)为前缀函数Pi[j]。

3.前缀函数的递推计算

根据上面的算法,我们同时得到前缀函数的值

下面说明递推的过程:
假设我们已经知道了j=1~4时Pi[j]的值了,如何推得Pi[5]和Pi[6]呢?

Pi[4]=2,说明字符串B[1..2]与B[3..4]相同,又B[5]==B[3],所以,P[5]=P[4]+1=3;

而Pi[6]却不等于Pi[5]+1,这是因为B[4]≠B[6],因此没法通过Pi[5]+1得到Pi[3],但同时,B[1],B[3],B[5]均为字符’a’,或许还可能通过Pi[3]或Pi[1]得到Pi[6],但一次次匹配都发现B[6]不等于B[4]且B[6]不等于B[2],这时,Pi[6]只能是0。

抽象一下,就得到Pi[j]的算法:
先用一个临时变量T=Pi[j-1]
若B[T] == B[j],那么Pi[j] = Pi[j-1]
否则,T=Pi[T],继续上面的操作,直到Pi[T] == 0,这时Pi[j]=0。

最后附上计算Pi[6]的示意图:

最后一部分参考了M67的文章,在此对其表示感谢。

http://www.matrix67.com/blog/archives/115

Arch Linux下面的goagent代理安装问题

2021.09.26 补充: 原文在 2011年10月31日发表在之前的 Blog 上,本文是基于 Internet Archive 上面的记录手动迁移过来的内容。 放在今天看这篇文章已经完全失去了价值,而且有不少错误的地方,因此只建议考古用,不建议参考内容。

以下为正文:

刚刚把Arch Linux下面的goagent代理解决了。不知道为什么,正常情况下从官方网站上下载的GoAgent在我的电脑上没法运行,在大多数发行版里面只要这样就可以了:

sudo python proxy.py

这样的结果就像在windows下面一样,shell会打出程序运行的log,但在我的ArchLinux上面却提示这样的信息:


不管python用2.6还是3都不行(python2.6没问题,这个失误了。也就数可以sudo python2 proxy.py或是sudo python26 proxy.py),想了半天也想不出来问题出在哪,只好作罢。

直到后来在AUR里面发现了GoAgent代理的包(Here),这时便感到了希望(Arch Linux是我唯一成功编译gcl的系统啊!)。下载下来压缩包,makepkg就行了。

稍微费点时间讲一下过程吧:把AUR压缩包下载下来以后,解压到~/goagent-git/下,之后进入~/goagent-git/文件夹:

su //之后输入root密码
makepkg -s --asroot
pacman -U goagent-git-20110813-1-i686.pkg.tar.xz

之后需要更改goagent的配置文件,放在/etc/goagent下面,只要更改[gae]下面的appid为自己的appid就可以了,有密码的话不要忘了把密码填在下面的password = 后面。

ArchLinux上的goagent把程序做成了deamon,这点很赞。因此以后启动还是停止代理服务都要容易得多。
正常情况下只需这样:

sudo /etc/rc.d/goagent start

就可以了。

而如果想在开机时启动代理的话,只需在/etc/rc.conf配置文件下面的最后一行的括号里面加上goagent就行了,像这样

DAEMONS = (... goagent ...)

PS:按照AUR上面的说法,goagent的log文件位于/etc/log/goagent,而pid文件位于/var/run/goagent.pid。