文章目录

  • 最长公共子序列
  • 求解最长公共子序列
    • 确定状态转移方程
  • 如何求出最长的公共子序列
  • 如何实现文本比对
  • 比对效果图
  • 参考文章:

最近因为项目需求需要实现一个文本比对的功能,自然的就想到了git的文本比对功能,于是网上查阅了一些资料,看到了一个关键字(最长公共子序列),感觉又回到了大学刷题的时候了。

最长公共子序列

引用LeetCode第1143题的描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列
是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 例如,“ace”
是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

输入:text1 = “abcde”, text2 = “ace” 输出:3 解释:最长公共子序列是 “ace”,它的长度为 3。
示例 2:

输入:text1 = “abc”, text2 = “abc” 输出:3 解释:最长公共子序列是 “abc”,它的长度为 3。 示例 3:

输入:text1 = “abc”, text2 = “def” 输出:0 解释:两个字符串没有公共子序列,返回 0。

提示:

1 <= text1.length <= 1000 1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。

从上面可以知道最长公共子序列是指两个字符串里面连续不一定相邻的最长的公共字符

求解最长公共子序列

最长公共子序列的问题可以使用dp(动态规划)的方式来求解,但是使用动态规划需要确定状态转移方程。
首先我们假定需要求解的字符串是 sda3b225cwsadbs 和 ass3bcdssd2cpsekld998l ,其中他们的公共子串是 s3b2csd

确定状态转移方程

我们可以使用一个比较简单的例子来推导状态转移方程
比如 1a2b3cdef , asdcdrf 子串为 acdf
首先我们假设现在有一个方法可以计算出最长的公共子序列,我们把它设为 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
上面的推导里面可以得知
如果我们需要需要求出字符串 1a和as的最长公共子序列那么我们需要知道 字符串 1,as 以及 字符串1a,a 的最长公共子序列。
因此我们可以得到如下公式:
已知 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) )
根据上面的状态转移方程我们可以很轻易的写出对应的代码

 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);}

如何求出最长的公共子序列

在上面的动态规划代码里面我们已经得到了一个状态矩阵
如下图:

通过动态规划矩阵找出最长的子序列,我们只需要通过回溯dp数组就可以找出子序列
我们从最后一个位置开始往前回溯,分别判断左边和上边与当前位置是否相等,如果相等那么向左或者向上移动一位,如果都不相等那么这个就是我们需要的字符,记录下来,然后同时向左和向上移动一位
但是我们在通过回溯求出最长的公共子序列的时候会遇到多解的问题,如果左边和上边都与当前位置相等怎么办,这时候既可以向左也可以向上,不过得出来的结果可能会不一样
下面这张图的结果是采用了默认向上的做法,得出来的子序列是: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;}

如何实现文本比对

当我们求出来最长的公共子序列之后那么我们如何对这两个字符串进行比对
现在有如下两个字符串
str1= 1a2b3cdef
str2= asdcdrf
公共子序列为 acdf
如下图,我们以公共子串为标准进行错位对比,可以看到,我们能够很容易的分辨出
在str1上面新增了一个字符串 a,同时str1上面的 2b3 被修改成了 sd ,e 被修改成了f

通过上面的对比,我们只需要把元素换成字符串就可以进行文本比对了,代码如下

 /*** 比较文本,如果两个相等的串之间有多个串,并且数量不相等且都不为0,那么认为是无法准确比较* 那么在后面的标记过程中就不需要逐字比较* 采用首部优先进行比较* @param source 源数据* @param target 对比数据* @return 文本比对结果*/@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);}/*** 比较文本,如果两个相等的串之间有多个串,并且数量不相等且都不为0,那么认为是无法准确比较* 那么在后面的标记过程中就不需要逐字比较* 采用首部优先进行比较* @param lcsResult 最长公共子序列结果* @param source 源数据* @param target 对比数据*/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";/*** 内容行标记,删除还是新增还是修改*/private String tag;/*** 旧文本*/private String oldText;/*** 新文本*/private String newText;/*** 是否需要逐字比对样式,如果不需要,那么就整体标记*/private boolean isNeedCheckDetail;}

比对效果图

当然,效果图是Word的文本比对,其中还有很大一部分关于Word方面的代码,就不贴出来了,核心思想就是上面的文本比对了

参考文章:

类似git/linux的文件对比功能(diff)是怎么实现的?
最长公共子串

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

  1. 算法设计 - LCS 最长公共子序列最长公共子串 LIS 最长递增子序列

    出处 http://segmentfault.com/blog/exploring/ 本章讲解: 1. LCS(最长公共子序列)O(n^2)的时间复杂度,O(n^2)的空间复杂度: 2. 与之类似但不 ...

  2. LCS最长公共子序列和LIS最长上升子序列——例题剖析

    一.LCS最长公共子序列 最长公共子序列(LCS)问题算法详解+例题(转换成LIS,优化为O(nlogn),看不懂你来打我) longest comment subsequence 模板题 longe ...

  3. LCS(最长公共子序列)及其O(n)空间优化,O(nlogn)时间复杂度优化

    LCS(最长公共子序列)及其O(n)空间优化,O(nlogn)时间复杂度优化 n^2 的版本 int LCS_n_2(vector<char> &a,vector<char& ...

  4. java lcs_Java算法之最长公共子序列问题(LCS)实例分析

    本文实例讲述了Java算法之最长公共子序列问题(LCS).分享给大家供大家参考,具体如下: 问题描述:一个给定序列的子序列是在该序列中删去若干元素后得到的序列.确切地说,若给定序列X= { x1, x ...

  5. LCS/最长公共子序列/最长公共子串 实现 Python/Java

    参考 http://blog.csdn.net/u012102306/article/details/53184446 http://blog.csdn.net/hrn1216/article/det ...

  6. 动态规划求解LCS最长公共子序列问题c++

    文章目录 求最长公共子序列 最长公共子序列问题 刻画最长公共子序列的特征 递归公式 C++代码 求最长公共子序列长度 打印任意一组最长公共子序列 模板题 求最长公共子序列 最长公共子序列问题 (Lon ...

  7. LCS(最长公共子序列)

    题意描述 求两个字符串的最长公共子序列的长度 动态规划 用二维数组 C[i][j] 记录串x1x2⋯xi与y1y2⋯yj 的 LCS长度,则可得到状态转移方程: 代码实现: #include<i ...

  8. 大白话讲解LCS(最长公共子序列)

    今天看了七月在线算法课.再一次认识了LCS,现在整理记录: LCS(Longest Common Subsequence)最长公共子序列. 一个序列S任意删除若干个字符得到新序列T,那么T叫做S的子序 ...

  9. LCS最长公共子序列(最优线性时间O(n))

    这篇日志主要为了记录这几天的学习成果. 最长公共子序列根据要不要求子序列连续分两种情况. 只考虑两个串的情况,假设两个串长度均为n. 一,子序列不要求连续. (1)动态规划(O(n*n)) (转自:h ...

  10. POJ 1458 Common Subsequence DP LCS 最长公共子序列

    最长公共子序列,照抄<算法设计与分析导论>P138-140 设输入的两个字符串分别为a1,a2,```,am(串a) b1,b2,````,bn(串b) 设d(i,j)为字符串a1,a2, ...

最新文章

  1. 库克喜提 8 亿年终奖,2020 年整体薪酬增长 28%
  2. C#网络编程系列文章索引
  3. SDUT-2132_数据结构实验之栈与队列二:一般算术表达式转换成后缀式
  4. 2021年新高考八省联考成绩查询辽宁,辽宁八省联考成绩什么时候出 辽宁八省联考成绩查询入口...
  5. tableau地图城市数据_Tableau 地图 | 无法识别的城市
  6. 各个层次的gcc警告
  7. Redis持久化机制(RDB VS AOF)
  8. [多重背包+二进制优化]HDU1059 Dividing
  9. 弦图与完美消除序列(bzoj 1006: [HNOI2008]神奇的国度)
  10. JavaScript 模拟重载
  11. Git详解(2)——Git基础
  12. Mysql密码加密方式
  13. 实例篇——springboot自定义拦截器
  14. PHP用户名和密码登陆验证代码
  15. Qt安卓开发环境搭建
  16. 大国崛起:数据库领域的中国力量
  17. Win10系统磁盘分区图文教程
  18. 网络广告中各种广告形式
  19. Vivado使用技巧(2):综合运行与OOC
  20. option样式美化 css,CSS select样式优化

热门文章

  1. 如何确认某日是否为工作日(休息日)
  2. 基于matlab的绿色番茄识别定位方法
  3. mysql 数据库监控
  4. 收到字节跳动的面试邀请,我却掉了链子
  5. 关于红队武器库项目介绍
  6. 携程网涉嫌非法经营案宣判 携程最终胜诉
  7. html css 图片底部空,图片下面出现空白怎么解决_html/css_WEB-ITnose
  8. java 井字棋 人机_井字棋(人机对战版)
  9. 姓氏头像框一键制作小程序源码+附微语模块
  10. java新人培训_Java新人学习必要的步骤以及全套学习路线!