日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

JAVA实现基于LCS(最长公共子序列)的文本比对

發(fā)布時(shí)間:2023/12/31 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA实现基于LCS(最长公共子序列)的文本比对 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 最長(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)的代碼

public LcsResult lcs(List<String> source, List<String> target){int[][] dp = new int[source.size()+1][target.size()+1];int max = 0;for (int i = 1; i < (source.size() + 1); i++) {for (int j = 1; j < (target.size() + 1); j++) {String str1 = source.get(i-1);String str2 = target.get(j-1);if(str1.equals(str2)){int temp =dp[i][j] =dp[i-1][j-1]+1;max = Math.max(max,temp);}else{dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);}}}return findStr(dp,target);}

如何求出最長(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

代碼如下:

private LcsResult findStr(int[][] dp, List<String> target) {LcsResult lcsResult = new LcsResult();lcsResult.setCommonString(new ArrayList<>());lcsResult.setSourceIndex(new ArrayList<>());lcsResult.setTargetIndex(new ArrayList<>());int i=dp.length-1;for (int j = dp[i].length-1; j > 0 && i>0;) {int k = dp[i][j];int up = dp[i-1][j];int left = dp[i][j-1];if(up==k ){i--;} else if(left==k) {j--;}else{int targetIndex= j-1;int sourceIndex = i-1;lcsResult.getSourceIndex().add(sourceIndex);lcsResult.getTargetIndex().add(targetIndex);lcsResult.getCommonString().add(target.get(targetIndex));i--;j--;}}Collections.reverse(lcsResult.getCommonString());Collections.reverse(lcsResult.getSourceIndex());Collections.reverse(lcsResult.getTargetIndex());return lcsResult;}

如何實(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ì)了,代碼如下

/*** 比較文本,如果兩個(gè)相等的串之間有多個(gè)串,并且數(shù)量不相等且都不為0,那么認(rèn)為是無(wú)法準(zhǔn)確比較* 那么在后面的標(biāo)記過(guò)程中就不需要逐字比較* 采用首部?jī)?yōu)先進(jìn)行比較* @param source 源數(shù)據(jù)* @param target 對(duì)比數(shù)據(jù)* @return 文本比對(duì)結(jié)果*/@Overridepublic List<CompareResult> compare(List<String> source, List<String> target) {LCS lcs = new LCS();LcsResult lcsResult = lcs.lcs(source, target);return compare(lcsResult,source,target);}/*** 比較文本,如果兩個(gè)相等的串之間有多個(gè)串,并且數(shù)量不相等且都不為0,那么認(rèn)為是無(wú)法準(zhǔn)確比較* 那么在后面的標(biāo)記過(guò)程中就不需要逐字比較* 采用首部?jī)?yōu)先進(jìn)行比較* @param lcsResult 最長(zhǎng)公共子序列結(jié)果* @param source 源數(shù)據(jù)* @param target 對(duì)比數(shù)據(jù)*/public List<CompareResult> compare(LcsResult lcsResult, List<String> source, List<String> target) {List<CompareResult> res = new ArrayList<>();int lastSourceIndex = 0;int lastTargetIndex = 0;for (int i = 0; i < lcsResult.getCommonString().size(); i++) {int sourceIndex = lcsResult.getSourceIndex().get(i);int targetIndex = lcsResult.getTargetIndex().get(i);List<String> sourceTemp = source.subList(lastSourceIndex,sourceIndex);List<String> targetTemp = target.subList(lastTargetIndex,targetIndex);compareLeftAndRight(sourceTemp,targetTemp,res);lastSourceIndex = sourceIndex+1;lastTargetIndex = targetIndex+1;res.add(new CompareResult(CompareResult.RESULT_EQUAL,source.get(sourceIndex),target.get(targetIndex),true));}List<String> sourceTemp = lastSourceIndex>=source.size()?new ArrayList<>():source.subList(lastSourceIndex,source.size());List<String> targetTemp = lastTargetIndex>=target.size()?new ArrayList<>():target.subList(lastTargetIndex,target.size());compareLeftAndRight(sourceTemp,targetTemp,res);return res;}private void compareLeftAndRight(List<String> sourceTemp, List<String> targetTemp, List<CompareResult> res) {if(CollectionUtils.isEmpty(sourceTemp)){targetTemp.forEach(item-> res.add(new CompareResult(CompareResult.RESULT_INSERT,"",item,true)));}else if(CollectionUtils.isEmpty(targetTemp)){sourceTemp.forEach(item-> res.add(new CompareResult(CompareResult.RESULT_DELETE,item,"",true)));}else if(targetTemp.size()==sourceTemp.size()){for (int k = 0; k < targetTemp.size(); k++) {res.add(new CompareResult(CompareResult.RESULT_CHANGE,sourceTemp.get(k),targetTemp.get(k),true));}}else if(sourceTemp.size()>targetTemp.size()){for (int k = 0; k < sourceTemp.size(); k++) {res.add(new CompareResult(k>=targetTemp.size()?CompareResult.RESULT_DELETE:CompareResult.RESULT_CHANGE,sourceTemp.get(k),k>=targetTemp.size()?"":targetTemp.get(k),false));}}else{for (int k = 0; k < targetTemp.size(); k++) {res.add(new CompareResult(k>=sourceTemp.size()?CompareResult.RESULT_INSERT:CompareResult.RESULT_CHANGE,k>=sourceTemp.size()?"":sourceTemp.get(k),targetTemp.get(k),false));}}}

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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。