應用
- 圖形相似處理
- 媒體流的相似比較
- 描述文本相似度 來辨別抄襲
定義
- 最長公共子序列
- 一個序列S任意刪除若干個字符得到的新字符T,則T叫做S的子序列
- 兩個序列X和Y的公共子序列中,長度最長的那個,定義爲X和Y的最長公共子序列
- 字符串13455與245576 的最長公共子序列是455
- 字符串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
}
}