《列划分算法,使得这个本就很牛的算法性能直接提高一倍.doc》由会员分享,可在线阅读,更多相关《列划分算法,使得这个本就很牛的算法性能直接提高一倍.doc(4页珍藏版)》请在三一文库上搜索。
1、列划分算法,使得这个本就很牛的算法性能直接提高一倍前言我最近一直在公司做检索性能优化。当我看到这个算法之前,我也不认为我负责的检索系统性能还有改进的余地。但是这个算法确实太牛掰了,足足让服务性能提高50%,我不得不和大家分享一下。其实前一段时间的博客中也写到过这个算法,只是没有细讲,今天我准备把它单独拎出来,说道说道。说实话,本人数学功底一般,算法证明不是我强项,所以文中的证明只是我在论文作者的基础上加入了自己的思考方法,并且还没有完全证明出来,请大家见谅 ! 欢迎爱思考的小伙伴进行补充。我只要达到抛砖引玉的作用,就知足了。回归正题,我们的检索服务中用到了最小编辑距离算法,这个算法本身是平方量
2、级的时间复杂度,并且很少人在帖子中提到小于这个复杂度的算法。但是我无意中发现了另外一个更牛的算法:列划分算法,使得这个本就很牛的算法性能直接提高一倍。接下来进入正题。列划分算法这个算法比较难理解,出自如下论文:Theoretical and empirical comparisons of approximate string matching algorithms。In Proceedings of the 3rd Annual Symposium on Combinatorial Pattern Matching, number 664 in Lecture Notes in Comput
3、er Science, pages 175184. Springer-Verlag, 1992。Author:WI Chang ,J Lampe。所以有必要先给大家普及一些共识。编辑矩阵最小编辑距离在计算过程中使用动态规划算法计算的那个矩阵,了解这个算法的都懂,我不赘述。但是我们的编辑矩阵有个特点:第一行都是0,这么做的好处是:只要文本串T中的任意一个子序列与模式串P的编辑距离小于某个固定的数值,就会被发现。给大伙一个样例,文本串T=annealing,模式串P=annual:注意,第一行都是0,这是与传统最小编辑距离的最大区别,其余的动归方程完全相同。对角线法则编辑矩阵沿着右下方对角线方向数
4、值非递减,并且至多相差1。行列法则每行每列相邻两个数至多相差1。观察编辑距离矩阵,我们发现如下事实:每一列是由若干段连续数字组成。所以我们把编辑矩阵的每一列划分成若干连续序列,如下图所示:红色框中就是一个一个的序列,序列内部连续。序列- 定义对于编辑矩阵的每一个元素Dji (j是行,i是列),若 j Dji = ,我们就说Dji属于i列上的 序列-,我们还观察到随着j增大,j Dji是非递减的。如下图所示:序列-终止位置每个序列都会有起始和终止位置。序列-的终止位置为j,如果j是序列-的最小横坐标,并且满足Dj+1i属于序列-,并且(即j+1-Dj+1i)。长度为0的序列我们发现如果按照如上定
5、义,每一列上的值并不一定连续,总是或有或无的缺少一个数值。所以我们定义长度为0的序列:当Dj+1i 所以,我们按照这个定义,就可以对编辑矩阵的每列进行一个划分,划分的每一段都是一串连续数字。说了这么多,这个定义有什么用呢?假若,我们每次都能根据前一列的列划分情况直接推导出后一列的列划分情况,那么就可以省去好多计算,毕竟每一个划分中的每一段的数字都是连续的,这就暗示我们可以直接用一个常数时间的加法直接得到某一个编辑矩阵的元素值,而不用使用最小编辑距离的动态规划算法去计算。接下来的重点来了,我们介绍这个推导公式,请打起十二分精神!我们按照序列-长度是否为0来介绍这个推论。由于其中一个推论文字描述太
6、繁琐,不容易理解,所以我画了个图:接下来烧脑开始。推论1:如果列i上长度为0的 序列- 的结束位置为j,则列i+1上的 序列- 的结束位置为 j+1。证明:由推论前提我们知道 = j Dji + 1 (想想前面说的值不连续,我们就人为插入一个中间值,只不过长度为0)。我们观察编辑矩阵就会发现如下两个事实:事实1:Dj+1i+1 = Dji ( 别问为什么, 自己观察, 看看是不是都这样, 其实可以用反证法,我们就不证明了)。事实2:Dj+2i+1 = Dj-Li;综上两点我们得到如下大小关系:Dj-L+1i+1 = Dj-L+1i。此外我们知道我们当前列的序列-截止位置为j,也意味着Dj+1i
7、 = A,Dj+2i+1 = A+L+2-1= A+L+1,与我们先前的推导矛盾。所以,在j-L+1和j+2之间一定有一个列终止,这样才能消去一个序号。此外我们还有一个疑问,列i+1上的序列-结束位置一定在j-L+1和j+1之间么?我们要证明这个事。证明:因为=j-Dji=j-L+1-Dj-L+1i=j-L+1-Dj-L+1i+1,即列i+1上的 序列-的结束位置一定在j-L+1或者之后;由于j+1-Dj+1i,根据对角线法则Dj+2i+1 =j+2-(Dj+1i+1)=j+1-Dj+1i , 固列i+1上的序列-的终止位置一定在j+2之前,即j-L+1到j+1之间。后面推论2的分情况讨论,我
8、一个也没证明出来,作者在论文中轻飘飘的一句话“后面很好证明,他就不去证明了”,但是却消耗了我所有脑细胞。所以,如果哪位小伙伴把推论2剩下的内容证明出来了,欢迎给我留言,我也学习学习。这个算法的时间复杂度是多少呢?作者用启发式的方法证明了算法的复杂度约为$ O(mn/sqrt2b) $,其中b是字符集大小。代码实现接下来说一下代码实现,给出我总结出来的步骤,否则很容易踩坑。编辑矩阵第一列,肯定只有一个序列。每次遍历前一列的所有序列,根据推论1和推论2计算后一列的划分情况。如果前一列遍历完毕,但是下一列还有剩余的元素没有划分。没关系,下一列剩下的元素都归为一个新的序列。预处理一个表,表中记录T中的
9、每个字符在P中的位置。可以直接用哈希算法(最好直接ascii码)进行定位,如果位置不唯一,可以拉链。进行列划分计算时,从前往后遍历那一链上的位置,直到找到第一个符合条件的,速度出奇的快。尽可能少使用或者不要使用map进行定位,测试发现相当慢。接下来做最不愿意做的事:贴一个代码,很丑。inlineintloc(intfind200,int*len,intch,intpos)for(inti=0;i=pos)returnfindchi;return-1;intnew_column_partition(char*p,char*t)intlen_p=strlen(p);intlen_t=strlen(t);intfind26200;intlen26=0;intpart200;/记录每一个序列的结束位置/生成loc表,用来快速查询for(inti=0;i=1if(lenti-a0a,b)!=-1elseif(pre_cn=2elsepartnext_cn+=part0;/每列第一个partition尾值tmp_value=part0;/遍历前一列剩下的partitionfor(intj=1;j0a,b)!=-1elseif(j+1CPU:优化后CPU:能力有限,证明不充分,有兴趣的小伙伴可以直接去看原版论文,欢迎交流,共同进步。
链接地址:https://www.31doc.com/p-3393294.html