JAVA实现基于LCS(最长公共子序列)的文本比对
文章目錄
- 最長(zhǎng)公共子序列
- 求解最長(zhǎng)公共子序列
- 確定狀態(tài)轉(zhuǎn)移方程
- 如何求出最長(zhǎng)的公共子序列
- 如何實(shí)現(xiàn)文本比對(duì)
- 比對(duì)效果圖
- 參考文章:
最近因?yàn)轫?xiàng)目需求需要實(shí)現(xiàn)一個(gè)文本比對(duì)的功能,自然的就想到了git的文本比對(duì)功能,于是網(wǎng)上查閱了一些資料,看到了一個(gè)關(guān)鍵字(最長(zhǎng)公共子序列),感覺(jué)又回到了大學(xué)刷題的時(shí)候了。
最長(zhǎng)公共子序列
引用LeetCode第1143題的描述
給定兩個(gè)字符串 text1 和 text2,返回這兩個(gè)字符串的最長(zhǎng)公共子序列的長(zhǎng)度。
一個(gè)字符串的 子序列
是指這樣一個(gè)新的字符串:它是由原字符串在不改變字符的相對(duì)順序的情況下刪除某些字符(也可以不刪除任何字符)后組成的新字符串。 例如,“ace”
是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。兩個(gè)字符串的「公共子序列」是這兩個(gè)字符串所共同擁有的子序列。
若這兩個(gè)字符串沒(méi)有公共子序列,則返回 0。
示例 1:
輸入:text1 = “abcde”, text2 = “ace” 輸出:3 解釋:最長(zhǎng)公共子序列是 “ace”,它的長(zhǎng)度為 3。
示例 2:
輸入:text1 = “abc”, text2 = “abc” 輸出:3 解釋:最長(zhǎng)公共子序列是 “abc”,它的長(zhǎng)度為 3。 示例 3:
輸入:text1 = “abc”, text2 = “def” 輸出:0 解釋:兩個(gè)字符串沒(méi)有公共子序列,返回 0。
提示:
1 <= text1.length <= 1000 1 <= text2.length <= 1000 輸入的字符串只含有小寫(xiě)英文字符。
從上面可以知道最長(zhǎng)公共子序列是指兩個(gè)字符串里面連續(xù)不一定相鄰的最長(zhǎng)的公共字符
求解最長(zhǎng)公共子序列
最長(zhǎng)公共子序列的問(wèn)題可以使用dp(動(dòng)態(tài)規(guī)劃)的方式來(lái)求解,但是使用動(dòng)態(tài)規(guī)劃需要確定狀態(tài)轉(zhuǎn)移方程。
首先我們假定需要求解的字符串是 sda3b225cwsadbs 和 ass3bcdssd2cpsekld998l ,其中他們的公共子串是 s3b2csd
確定狀態(tài)轉(zhuǎn)移方程
我們可以使用一個(gè)比較簡(jiǎn)單的例子來(lái)推導(dǎo)狀態(tài)轉(zhuǎn)移方程
比如 1a2b3cdef , asdcdrf 子串為 acdf
首先我們假設(shè)現(xiàn)在有一個(gè)方法可以計(jì)算出最長(zhǎng)的公共子序列,我們把它設(shè)為 lcs(m,n)
那么 lcs(1,"") = 0,lcs("",a) = 0,lcs(1,a) = 0,lcs(1a,a)=1 ,lcs(1,as) =0,lcs(1a,as) =1
上面的推導(dǎo)里面可以得知
如果我們需要需要求出字符串 1a和as的最長(zhǎng)公共子序列那么我們需要知道 字符串 1,as 以及 字符串1a,a 的最長(zhǎng)公共子序列。
因此我們可以得到如下公式:
已知 lcs(x,"") =0 , lcs("",y) =0
那么 lcs(x,y) 可以求解:
if x=y
lcs(x,y) = max(lcs(x,"") , lcs("",y) ) +1
else
lcs(x,y) = max(lcs(x,"") , lcs("",y) )
根據(jù)上面的狀態(tài)轉(zhuǎn)移方程我們可以很輕易的寫(xiě)出對(duì)應(yīng)的代碼
如何求出最長(zhǎng)的公共子序列
在上面的動(dòng)態(tài)規(guī)劃代碼里面我們已經(jīng)得到了一個(gè)狀態(tài)矩陣
如下圖:
通過(guò)動(dòng)態(tài)規(guī)劃矩陣找出最長(zhǎng)的子序列,我們只需要通過(guò)回溯dp數(shù)組就可以找出子序列
我們從最后一個(gè)位置開(kāi)始往前回溯,分別判斷左邊和上邊與當(dāng)前位置是否相等,如果相等那么向左或者向上移動(dòng)一位,如果都不相等那么這個(gè)就是我們需要的字符,記錄下來(lái),然后同時(shí)向左和向上移動(dòng)一位
但是我們?cè)谕ㄟ^(guò)回溯求出最長(zhǎng)的公共子序列的時(shí)候會(huì)遇到多解的問(wèn)題,如果左邊和上邊都與當(dāng)前位置相等怎么辦,這時(shí)候既可以向左也可以向上,不過(guò)得出來(lái)的結(jié)果可能會(huì)不一樣
下面這張圖的結(jié)果是采用了默認(rèn)向上的做法,得出來(lái)的子序列是:acdf
代碼如下:
如何實(shí)現(xiàn)文本比對(duì)
當(dāng)我們求出來(lái)最長(zhǎng)的公共子序列之后那么我們?nèi)绾螌?duì)這兩個(gè)字符串進(jìn)行比對(duì)
現(xiàn)在有如下兩個(gè)字符串
str1= 1a2b3cdef
str2= asdcdrf
公共子序列為 acdf
如下圖,我們以公共子串為標(biāo)準(zhǔn)進(jìn)行錯(cuò)位對(duì)比,可以看到,我們能夠很容易的分辨出
在str1上面新增了一個(gè)字符串 a,同時(shí)str1上面的 2b3 被修改成了 sd ,e 被修改成了f
通過(guò)上面的對(duì)比,我們只需要把元素?fù)Q成字符串就可以進(jìn)行文本比對(duì)了,代碼如下
CompareResult
@Data @AllArgsConstructor public class CompareResult implements Serializable {public static final String RESULT_EQUAL = "EQUAL";public static final String RESULT_INSERT = "INSERT";public static final String RESULT_DELETE = "DELETE";public static final String RESULT_CHANGE = "CHANGE";/*** 內(nèi)容行標(biāo)記,刪除還是新增還是修改*/private String tag;/*** 舊文本*/private String oldText;/*** 新文本*/private String newText;/*** 是否需要逐字比對(duì)樣式,如果不需要,那么就整體標(biāo)記*/private boolean isNeedCheckDetail;}比對(duì)效果圖
當(dāng)然,效果圖是Word的文本比對(duì),其中還有很大一部分關(guān)于Word方面的代碼,就不貼出來(lái)了,核心思想就是上面的文本比對(duì)了
參考文章:
類(lèi)似git/linux的文件對(duì)比功能(diff)是怎么實(shí)現(xiàn)的?
最長(zhǎng)公共子串
總結(jié)
以上是生活随笔為你收集整理的JAVA实现基于LCS(最长公共子序列)的文本比对的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ios swift5 自定义初始化方法
- 下一篇: 公开课|“隐私计算+区块链”安全解锁数据