LCS最長公共子序列

應用

  • 圖形相似處理
  • 媒體流的相似比較
  • 描述文本相似度 來辨別抄襲

定義

  • 最長公共子序列
  • 一個序列S任意刪除若干個字符得到的新字符T,則T叫做S的子序列
  • 兩個序列X和Y的公共子序列中,長度最長的那個,定義爲X和Y的最長公共子序列
    1. 字符串13455與245576 的最長公共子序列是455
    2. 字符串acdfg 與adfc 的最長公共子序列是adf
  • 最長公共子序列要求連續

窮舉法

假設字符型X 和 字符串Y的長度分別爲m 和 n

X的一個子序列即下標{1,2,3,...,m}嚴格遞增子序列 , 因此X共有2m個不同的子序列,同理Y也有2n

從窮舉搜索法需要的時間複雜度 O(2m * 2n)

這種方式顯然是不可取的: 對X的每一個序列,檢查它是否在Y的子序列從而確定它是否爲X和Y的公共子序列,並且在檢查過程中選出最長公共子序列

動態規劃法

  • 字符串X ,長度爲m ,從1開始數
  • 字符串Y ,長度爲n ,從1開始數
  • Xi = <X1,...,Xi > 即X序列的前i個字符(1<=i<=m) (Xi 記作“字符串X的i前綴”)
  • Yj = <Y1,...,Yj > 即Y序列的前i個字符(1<=j<=n) (Yj 記作“字符串Y的i前綴”)
  • LCS(X,Y)爲字符串X和Y的最長公共子序列,即<Z1,...,Zk >
  • 如果Xm = Yn(最後一個字符相同),則:Xm 和 Yn的最長公共子序列Zk的最後一個字符必定爲Xm = (Yn)
  • Zk = Xm = Yn
  • 推導出公式

LCS(X_m,Y_n)=LCS(X_{m-1} , Y_{n-1})+X_mLCS(Xm​,Yn​)=LCS(Xm−1​,Yn−1​)+Xm​

例如: 字符串 BDC 和 字符串 ABC

對於上面字符的X和Y

  • 找Xm = Ym
  • X3 = Y3
  • LCS(BDC,ABC) = LCS(BD,AB) + C
  • 如果Xm !=Yn

LCS(X_m,Y_n)=max\{LCS(X_{m-1} , Y_{n}) ,LCS(X_{m} , Y_{n-1}\}LCS(Xm​,Yn​)=max{LCS(Xm−1​,Yn​),LCS(Xm​,Yn−1​}

  • X2 != Y2
  • LCS(BD,AB) =max{LCS(BD,A),LCS(B,AB)}
  • 得到公式

LCS(X_m,Y_m) = \begin{cases} LCS(X_{m-1},Y_{n-1})+Xm &{\text{當}}X_m=Y_m \\ max\{LCS(X_m,Y_{n-1}),LCS(x_{m-1},Y_{n}) \}&{\text{當}}X_m!=Y_m \\ \end{cases}LCS(Xm​,Ym​)={LCS(Xm−1​,Yn−1​)+Xmmax{LCS(Xm​,Yn−1​),LCS(xm−1​,Yn​)}​當Xm​=Ym​當Xm​!=Ym​​

如果使用二維數組C[m][n]

  • C[i,j]記錄序列Xi和Yj的最長公共子序列的長度

  • X=acdfg

  • Y=adfc

      a  d  f  c
  [0. 0. 0. 0. 0.]
a [0. 1. 1. 1. 1.]
c [0. 1. 1. 1. 2.]
d [0. 1. 2. 2. 2.]
f [0. 1. 2. 3. 3.]
g [0. 1. 2. 3. 3.]

--得到的最長公共子序列長度爲3
--相似度則爲 3*2/(5+4) = 0.67
  • 最終得到公式

c(i,j)= \begin{cases} 0 &{\text{當i=0或者j=0}} \\ c(i-1,j-1)+1 &{\text{當i,j>0 並且}}X_i=Y_j \\ max\{c(i-1)(j),c(i,j-1)\} &{\text{當i,j>0 並且}}X_i !=Y_j\\ \end{cases}c(i,j)=⎩⎪⎨⎪⎧​0c(i−1,j−1)+1max{c(i−1)(j),c(i,j−1)}​當i=0或者j=0當i,j>0 並且Xi​=Yj​當i,j>0 並且Xi​!=Yj​​

相似度 = 最長公共子序列的長度 * 2 / 兩個對比文本的總長度

使用python來實現LCS

"""
LCS最長公共子序列對比文本相似度對比
"""


import numpy as np
# 反序
def lcs(str1,str2):
    list_np = np.zeros([len(str1)+1,len(str2)+1]) # 浮點類型的
    li1 = list(range(0,len(str1)))
    li1.reverse()
    li2 = list(range(0, len(str2)))
    li2.reverse()
    for i in li1:
        for j in li2:
            if str1[i] == str2[j]:
                list_np[i][j] = list_np[i+1][j+1] + 1
            else:
                list_np[i][j] = max(list_np[i+1][j],list_np[i][j+1])
    print(list_np)
    ret =  list_np[0][0] * 2 /(len(str1) +len(str2))
    return ret;

# 正序
def lcs2(str1,str2):
    list_np = np.zeros([len(str1) + 1, len(str2) + 1])  # 浮點類型的
    li1 = list(range(1, len(str1)+1))
    li2 = list(range(1, len(str2)+1))
    for i in li1:
        for j in li2:
            if str1[i-1] == str2[j-1]:
                list_np[i][j] = list_np[i-1][j-1]+1
            else:
                list_np[i][j] = max(list_np[i - 1][j ],list_np[i ][j - 1])
    print(list_np)
    ret = list_np[len(str1)][len(str2)] * 2 / (len(str1) +len(str2))
    return ret

def main():
    str1 = "adsadasdaa"
    str2 ="sdasdsadsa"
    print("文本相似度爲",lcs(str1,str2))

if __name__ == '__main__':
    main()

使用scala 來實現

object LCSDemo {
  def main(args: Array[String]): Unit = {
    val str1 = "acdfg"
    val str2 = "adfc"
    println(lcs(str1, str2))
  }

  def lcs(str1: String, str2: String): Double = {
    //Array.ofDim[Int](x,y) 生成一個二維數組初始值爲Int類型,並且維度爲x 和 y
    val list_arr = Array.ofDim[Int](str1.length + 1, str2.length + 1)
    for (i <- 1 to str1.length; j <- 1 to str2.length) {
      if (str1(i - 1) == str2(j - 1)) {
        list_arr(i)(j) = list_arr(i - 1)(j - 1) + 1
      } else {
        list_arr(i)(j) = list_arr(i)(j - 1).max(list_arr(i - 1)(j))
      }
    }
    val ret = list_arr(str1.length)(str2.length)
    ret * 2 / (str1.length + str2.length).toDouble
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章