• 文本比较算法Ⅱ——Needleman/Wunsch算法


      在“文本比较算法Ⅰ——LD算法”中介绍了基于编辑距离的文本比较算法——LD算法。

      本文介绍基于最长公共子串的文本比较算法——Needleman/Wunsch算法。

      还是以实例说明:字符串A=kitten,字符串B=sitting

      那他们的最长公共子串为ittn(注:最长公共子串不需要连续出现,但一定是出现的顺序一致),最长公共子串长度为4。

      

      定义:

      LCS(A,B)表示字符串A和字符串B的最长公共子串的长度。很显然,LSC(A,B)=0表示两个字符串没有公共部分。

      Rev(A)表示反转字符串A

      Len(A)表示字符串A的长度

      A+B表示连接字符串A和字符串B

      性质:

      LCS(A,A)=Len(A)

      LCS(A,"")=0

      LCS(A,B)=LCS(B,A)

      0≤LCS(A,B)≤Min(Len(A),Len(B))

      LCS(A,B)=LCS(Rev(A),Rev(B))

      LCS(A+C,B+C)=LCS(A,B)+Len(C)

      LCS(A+B,A+C)=Len(A)+LCS(B,C)

      LCS(A,B)≥LCS(A,C)+LCS(B,C)

      LCS(A+C,B)≥LCS(A,B)+LCS(B,C)

      为了讲解计算LCS(A,B),特给予以下几个定义

      A=a1a2……aN,表示A是由a1a2……aN这N个字符组成,Len(A)=N

      B=b1b2……bM,表示B是由b1b2……bM这M个字符组成,Len(B)=M

      定义LCS(i,j)=LCS(a1a2……ai,b1b2……bj),其中0≤i≤N,0≤j≤M

      故:  LCS(N,M)=LCS(A,B)

          LCS(0,0)=0

          LCS(0,j)=0

          LCS(i,0)=0

      对于1≤i≤N,1≤j≤M,有公式一

      若ai=bj,则LCS(i,j)=LCS(i-1,j-1)+1

      若ai≠bj,则LCS(i,j)=Max(LCS(i-1,j-1),LCS(i-1,j),LCS(i,j-1))

      计算LCS(A,B)的算法有很多,下面介绍的Needleman/Wunsch算法是其中的一种。和LD算法类似,Needleman/Wunsch算法用的都是动态规划的思想。在Needleman/Wunsch算法中还设定了一个权值,用以区分三种操作(插入、删除、更改)的优先级。在下面的算法中,认为三种操作的优先级都一样。故权值默认为1。

      

      举例说明:A=GGATCGA,B=GAATTCAGTTA,计算LCS(A,B)

      第一步:初始化LCS矩阵

    Needleman/Wunsch算法矩阵
        G A A T T C A G T T A
      0 0 0 0 0 0 0 0 0 0 0 0
    G 0                      
    G 0                      
    A 0                      
    T 0                      
    C 0                      
    G 0                      
    A 0                      

      第二步:利用公式一,计算矩阵的第一行

    Needleman/Wunsch算法矩阵
        G A A T T C A G T T A
      0 0 0 0 0 0 0 0 0 0 0 0
    G 0 1 1 1 1 1 1 1 1 1 1 1
    G 0                      
    A 0                      
    T 0                      
    C 0                      
    G 0                      
    A 0                      

       第三步:利用公式一,计算矩阵的其余各行

    Needleman/Wunsch算法矩阵
        G A A T T C A G T T A
      0 0 0 0 0 0 0 0 0 0 0 0
    G 0 1 1 1 1 1 1 1 1 1 1 1
    G 0 1 1 1 1 1 1 1 2 2 2 2
    A 0 1 2 2 2 2 2 2 2 2 2 2
    T 0 1 2 2 3 3 3 3 3 3 3 3
    C 0 1 2 2 3 3 4 4 4 4 4 4
    G 0 1 2 2 3 3 3 4 5 5 5 5
    A 0 1 2 3 3 3 3 4 5 5 5 6

      则,LCS(A,B)=LCS(7,11)=6

      可以看出,Needleman/Wunsch算法实际上和LD算法是非常接近的。故他们的时间复杂度和空间复杂度也一样。时间复杂度为O(MN),空间复杂度为O(MN)。空间复杂度经过优化,可以优化到O(M),但是一旦优化就丧失了计算匹配字串的机会了。由于代码和LD算法几乎一样。这里就不再贴代码了。

      

      还是以上面为例A=GGATCGA,B=GAATTCAGTTA,LCS(A,B)=6

      他们的匹配为:

        A:GGA_TC_G__A

        B:GAATTCAGTTA

      如上面所示,蓝色表示完全匹配,黑色表示编辑操作,_表示插入字符或者是删除字符操作。如上面所示,蓝色字符有6个,表示最长公共子串长度为6。

      利用上面的Needleman/Wunsch算法矩阵,通过回溯,能找到匹配字串

      第一步:定位在矩阵的右下角

    Needleman/Wunsch算法矩阵
        G A A T T C A G T T A
      0 0 0 0 0 0 0 0 0 0 0 0
    G 0 1 1 1 1 1 1 1 1 1 1 1
    G 0 1 1 1 1 1 1 1 2 2 2 2
    A 0 1 2 2 2 2 2 2 2 2 2 2
    T 0 1 2 2 3 3 3 3 3 3 3 3
    C 0 1 2 2 3 3 4 4 4 4 4 4
    G 0 1 2 2 3 3 3 4 5 5 5 5
    A 0 1 2 3 3 3 3 4 5 5 5 6

      第二步:回溯单元格,至矩阵的左上角

        若ai=bj,则回溯到左上角单元格

    Needleman/Wunsch算法矩阵
        G A A T T C A G T T A
      0 0 0 0 0 0 0 0 0 0 0 0
    G 0 1 1 1 1 1 1 1 1 1 1 1
    G 0 1 1 1 1 1 1 1 2 2 2 2
    A 0 1 2 2 2 2 2 2 2 2 2 2
    T 0 1 2 2 3 3 3 3 3 3 3 3
    C 0 1 2 2 3 3 4 4 4 4 4 4
    G 0 1 2 2 3 3 3 4 5 5 5 5
    A 0 1 2 3 3 3 3 4 5 5 5 6

         若ai≠bj,回溯到左上角、上边、左边中值最大的单元格,若有相同最大值的单元格,优先级按照左上角、上边、左边的顺序

    Needleman/Wunsch算法矩阵
        G A A T T C A G T T A
      0 0 0 0 0 0 0 0 0 0 0 0
    G 0 1 1 1 1 1 1 1 1 1 1 1
    G 0 1 1 1 1 1 1 1 2 2 2 2
    A 0 1 2 2 2 2 2 2 2 2 2 2
    T 0 1 2 2 3 3 3 3 3 3 3 3
    C 0 1 2 2 3 3 4 4 4 4 4 4
    G 0 1 2 2 3 3 3 4 5 5 5 5
    A 0 1 2 3 3 3 3 4 5 5 5 6

        若当前单元格是在矩阵的第一行,则回溯至左边的单元格

        若当前单元格是在矩阵的第一列,则回溯至上边的单元格

    Needleman/Wunsch算法矩阵
        G A A T T C A G T T A
      0 0 0 0 0 0 0 0 0 0 0 0
    G 0 1 1 1 1 1 1 1 1 1 1 1
    G 0 1 1 1 1 1 1 1 2 2 2 2
    A 0 1 2 2 2 2 2 2 2 2 2 2
    T 0 1 2 2 3 3 3 3 3 3 3 3
    C 0 1 2 2 3 3 4 4 4 4 4 4
    G 0 1 2 2 3 3 3 4 5 5 5 5
    A 0 1 2 3 3 3 3 4 5 5 5 6

        依照上面的回溯法则,回溯到矩阵的左上角

      第三步:根据回溯路径,写出匹配字串

        若回溯到左上角单元格,将ai添加到匹配字串A,将bj添加到匹配字串B

        若回溯到上边单元格,将ai添加到匹配字串A,将_添加到匹配字串B

        若回溯到左边单元格,将_添加到匹配字串A,将bj添加到匹配字串B

        搜索晚整个匹配路径,匹配字串也就完成了

      可以看出,LD算法和Needleman/Wunsch算法的回溯路径是一样的。这样找到的匹配字串也是一样的。

      不过,Needleman/Wunsch算法和LD算法一样,若要找出匹配字串,空间的复杂度就一定是O(MN),在文本比较长的时候,是极为耗用存储空间的。故若要计算出匹配字串,还得用其他的算法,留待后文介绍。

      

      

  • 相关阅读:
    Java 将Clob字段转换成String字符串
    Java BigDecimal详解
    java循环创建对象应该在循环体内还是循环体外
    Java 通过身份证获取生日和性别
    Java将数据进行分组处理
    Java 2进制和16进制的转换
    Map-HashMap 与 IF 判断内存占用对比
    Groovy脚本-通用SQL开关
    Oracle用户、授权、角色管理
    【docker随笔系列】安装sqlserver
  • 原文地址:https://www.cnblogs.com/grenet/p/1750454.html
Copyright © 2020-2023  润新知