`

从diff到LCS(Longest common subsequence),抽象之美

阅读更多

熟悉linux的朋友,对diff这个工具一定不会陌生。diff可以用来比对两份文件的异同。而在cvs svn这种版本控制系统中,diff更是发挥着重要作用 。

由于同一个项目有多个子版本,所以某一个子版本在进行了一些bug修复后,想把同样的修复应用到其它的版本上。使用cvs不知道是不是支持这种功能。所以,自己想写一个脚本,来将一些改动自动应用。

首先,想自己动手实现diff。

最初的想法是这样的:

 

1. 将2份文件的所有行读取到两个list中,src与tar,每个list设置2个游标,分别是Current 和 position,代表当前处理的行数以及在行与行不匹配的情况下,进行超前搜索。

2. 逐行进行比较

    2.1 如果src.current == tar.current,表示该行没有改动,Current++

    2.2 如果src.current !=  tar.current,表示两行不同

          先对tar进行超前搜索,判断是否存在一行,与src.current相同

  tar.position = tar.current+1

          while tar.psotion<tar.size

                    if tar.postion == src.current

                           那么在tar.current 与tar.postion之间的行,就可以认为是tar文件比src文件多的行数

                           tar.current=tar.postion+1

                           src.current++

                           break

                    tar.postion++

          如果在对tar文件搜索完全部行数后,仍未能找到一行满足tar.postion==src.current

          那么就对src进行超前搜索,来判断是否存在src.postion = tar.current

          while src.postion<src.size and tar.postion == tar.size <---表示tar进行了完全搜索,但为匹配

                   处理与对tar的处理一致

 

          最后,如果对src进行了全部搜索,也没有发现匹配行,那么就说明src.current 与 tar.current 是对应行,但是有一行内容发生了变化

          if src.postion == src.size

                src.current 是内容变化的行,不是新加入的行

 

首先,这个算法肯定是有bug的。这是对文本差异进行搜索时的第一时间的想法,就是对文本逐行扫描。在现实测试中,这个算法还是能够处理大部分情况的,但是,还是有错误,比如,假如对下面的文本进行处理:

 

          hello;                           hell;

          you;                             you;

          are;                             are;

          great;                          hello;

                                             great;

按照算法描述,最后会得出这样的结论,右侧的文本比左侧的文本多了hell;you;are;行,少了hello与great间的you;are;行。虽然,这样找出的也是两行文本的差异,但是,确切的说,这不是最好的答案,最起码,两个文件的差别没有那么大,仅仅是hello->hell和多了另一个hello。

 

所以,最开始想到的算法是不完备的,同时,也是混乱的,含着想当然的意味。

 

加入脱离现实业务本身,来对它进行一下抽象,我们会得到更好的解决方案。如果每一行只有一个字母的话,那么,就相当于寻找两个字符串的差异了。换句话说,要找出公共字符串的补集。然而,像 A-A^B 和 B-A^B这种集合运算,是不合适的,因为我们的 差异要涉及到顺序的概念。其实这就是lcs问题了。

所谓lcs,就是最长公共子串。子串的概念一定要清晰,就是对于一个串s0,s1...sn,保持顺序不变,从中抽取x个元素组成的新串。最长公共子串也就好理解了。有关概念问题,请参考最长公共子串

 

重新回到文件上来,只要我们能够知道两份文件的最长公共内容,我们就很容易的可以得到两份文件存在的差异了。最长公共子串的实现算法,使用递归可以更好的进行描述。

 

对于两个串A和B,假如A和B的最后一个元素相同,那么,这个元素必然在公共子串中。去掉A与B的最后一个元素,再对新串的最后一个元素对比,相同即去掉,直到发现不相同的元素。对于不相同的元素,我们先去掉A的最后一个元素,让新串与B进行递归处理,得到A‘与B的最长子串,然后去掉B的最后元素,让A和B’递归处理,得到A和B‘的最长子串,取这两个子串中较长的一个,就是A、B在最后一个元素不同时的最长子串。很显然,递归的终止条件是A和B有一个的长度变为0

 

Python的实现如下:

def lcs2(seqx,seqy):
    if len(seqx) == 0 or len(seqy)==0:
        return []
    if seqx[-1:] == seqy[-1:]:
        result = lcs2(seqx[:-1],seqy[:-1])
        result.append(seqx[-1:][0])
        return result
    else:
        t1 = lcs2(seqx[:-1],seqy)
        t2 = lcs2(seqx,seqy[:-1])
        t = t1 if len(t1)>len(t2) else t2
        return t

 这个递归调用,显然存在着非常大的冗余。和计算Fibonacci数列一样,每一个元素都会被计算多次。所以, 原始算法的效率非常低,对于一个n-n类型的串比较,最坏要发生n*∑2^(i-1)*i   1<=i<=n 次比较,复杂度为O(n^3*2^n)。如果我们能够避免重复的对某一已知情况进行求解,就可以大大简化这个过程。采用动态规划算法时,需要一个n*m的表,来保存所有已知的解。比如,我们从A0与B0开始,表示两个串均为空串。那么A2与B串的公共串,其实就是在已知A1与B’的公共串的基础上,如果A2的最后一个元素与B最后一个相同个,那么就是A1与B‘的公共串再添加上最后A2最后一个,否则,就是A1与B的和A2 B’的公共传中最长的一个。

Python实现:

def lcs3(seqx,seqy):
    table = []
    for i in range(len(seqx)+1):        
        table.append([0]*(len(seqy)+1))
    for xline in xrange(1,len(seqx)+1):
        for yline in xrange(1,len(seqy)+1):    
            if seqx[xline-1] == seqy[yline-1] :     
                table[xline][yline]=table[xline-1][yline-1]+1
            else:
                table[xline][yline] = table[xline][yline-1] if table[xline][yline-1]>table[xline-1][yline] else table[xline-1][yline]

 时间复杂度就是m*n,即n^2

此时,得到的table表,每一个表格的行号和列号对应了序列中的字符位置,(x,y)中的值就是长度为x和y的两个串的最长公共子串的长度。

 

 

通过最开始的文件差异对比,到后来的LCS算法,可以发现,通过进行抽象,问题得到了更好的理解和建模。抽象之美:)

分享到:
评论

相关推荐

    lcs.rar_Common Subsequence_LCS_lcs matlab_subsequence

    LCS Longest (maximum) common subsequence

    LCS(longest common substring)算法,即最大公共子串 C实现

    LCS(longest common substring)算法,即最大公共子串,它是求两个字符串最长公共子串的问题。大体解法是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0。然后求出对角线最长...

    LCS和CLCS的java实现

    Given two sequences X, Y and a constrained sequence P, a sequence Z is a constrained longest common subsequence for X and Y with respect to P if Z is the longest subsequence of X and Y such that P is...

    C# LCS 文本比较器

    项目需要一个文本比较器 自己根据LCS longest Common Subsequence 编写的比较器。功能精简可以根据我的代码在扩展支持其他格式的比较。 VS2008的项目

    LCS.rar_LCS

    longest common subsequence

    C#,最大公共子序列(LCS,Longest Common Subsequences)的算法与源代码

    C#,最大公共子序列(LCS,Longest Common Subsequences)的算法与源代码 最长的常见子序列问题是寻找两个给定字符串中存在的最长序列。 最大公共子序列算法,常用于犯罪鉴定、亲子鉴定等等的 DNA 比对。 1.1 子...

    git-diff-lcs

    GitDiffLCS 通常,git diff ...安装$ gem install git_diff-lcs如何使用$ git_diff_lcs shortstat [GIT_REPOSITORY or WORKING_DIRECTORY] [SRC(branch or commit)] [DEST(branch or commit)]$ git_diff_lcs shortstat ...

    lcs.rar_LCS

    Longest Common Subsequence Problem. Solution both Iterative and Recursive.

    实验九lcs算法.doc

    最长公共子序列(Longest Common Subsequence LCS)是从给定的两个序列X和Y中取出尽可能多的一部分字符,按照它们在原序列排列的先后次序排列得到。

    stl.LCS.rar_LCS

    参考算法导论写的LCS算法,仿照STL的泛型风格,适用于多种STL容器中的各种类型数据构成的序列的最大公共子序列(Longest Common Subsequence)问题求解。

    LCS.rar_连续子序列

    最长公共子序列也称作最长公共子串(不要求连续),英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知...

    字符串距离

    字符串a和b的LCS(Longest Common Subsequence)相似度是a和b间的最大相同子串的长度。显然LCS(i,j)越大,a,b越相似。 (3)N-gram相似度 设Ngram(a) 是字符串a中长度为N的子串的集合。两个字符串a,b的N-gram相似度NG...

    Experimenting an Approximation Algorithm

    The problem of nding the longest common subsequence (lcs) of a given set of se- quences over an alphabet occurs in many interesting contexts, such as data com- pression and molecular biology, in ...

    diff-lcs:生成Ruby序列之间的差异集

    Diff :: 家 代码 虫子 博士 持续集成 描述 Diff :: LCS使用McIlroy-Hunt最长公共子序列... 默认情况下,Diff :: LCS不会使用Diff :: LCS接口扩展对象,但是会像调用一个函数一样被调用: require 'diff/lcs' s

    lcs-image-diff-具有LCS算法的图像差异工具-Rust开发

    lcs-image-diff使用LCS算法的图像差异库和工具。 murooka的rust端口/ go-diff-image要求最新的Rust(推荐rustup)库lcs-image-diff使用LCS算法的图像diff库和工具。 murooka的rust端口/ go-diff-image要求最新的Rust...

    最长公共子序列问题.docx

    最长公共子序列(Longest Common Subsequence,LCS)问题是一个经典的动态规划问题,通常用于比较两个序列的相似程度。给定两个序列,找出它们之间的最长公共子序列的长度。 下面是一个 Python 的动态规划实现: `...

    LCS算法(连续)

    LCS(Longest common string)连续问题的求解,C++编写,稳定,高效.

    rubygem-diff-lcs-1.3-4.el8.noarch.rpm

    rpm安装包,rpm -i example.rpm

    rubygem-diff-lcs-1.2.5-7.el7.noarch.rpm

    官方离线安装包,亲测可用

    rubygem-diff-lcs-doc-1.2.5-7.el7.noarch.rpm

    官方离线安装包,亲测可用

Global site tag (gtag.js) - Google Analytics