算法系列之五:最长公共子序列(LCS)问题(非连续子序列)的两种解法

本文介绍了求解最长公共子序列(LCS)问题的两种方法:动态规划法和穷举法。动态规划法通过递推关系计算最优解,时间复杂度更优;而穷举法使用递归实现,适用于理论分析但实际效率较低。文章以具体示例阐述了两种方法的实现细节。
摘要由CSDN通过智能技术生成

算法系列之五:最长公共子序列(LCS)问题(非连续子序列)的两种解法

 

        最长公共子序列也称作最长公共子串,英文缩写是LCS(Longest Common Subsequence)。其定义是:一个序列S,如果分别是两个或多个已知序列的子序列,且是符合此条件的子序列中最长的,则称S为已知序列的最长公共子序列。

        关于子序列的定义通常有两种方式,一种是对子序列没有连续的要求,其子序列的定义就是原序列中删除若干元素后得到的序列。另一种是对子序列有连续的要求,其子序列的定义是原序列中连续出现的若干个元素组成的序列。求解子序列是非连续的最长公共子序列问题是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。本文将介绍对子序列没有连续性要求的情况下如何用计算机解决最长公共子序列问题,对子序列有连续性要求的情况下如何用计算机解决最长公共子序列问题将在后续的文章中介绍。

 

一、    动态规划法(Dynamic Programming)

 

        最长公共子序列问题应该是属于多阶段决策问题中求最优解一类的问题,凡此类问题在编制计算机程序时应优先考虑动态规划法,如果不能用动态规划法,而且也找不到其它解决方法,还可以考虑穷举法。对于这个问题,只要能找到描述最长公共子序列的最优子结构和最优解的堆叠方式,并且保证最优子结构中的每一次最优决策都满足“无后效性”,就可以考虑用动态规划法。使用动态规划法的关键是对问题进行分解,按照一定的规律分解成子问题(分解后的子问题还可以再分解,这是个递归的过程),通过对子问题的定义找出最优子结构中最优决策序列(对于子问题就是最有决策序列的子序列)以及最优决策序列子序列的递推关系(当然还包括递推关系的边界值)。

        如果一个给定序列的子序列是在该序列中删去若干元素后得到的序列,也就意味着子序列在原序列中的位置索引(下标)保持严格递增的顺序。例如,序列S = <B,C,D,B>是序列K = <A,B,C,B,D,A,B>的一个子序列(非连续),序列S的元素在在K中的位置索引I = [2,3,5,7],I是一个严格递增序列。

 

1.1           最优子结构定义与边界值

        现在来分析一下本问题的最优子结构。首先定义问题,假设有字符串str1长度为m,字符串str2长度为n,可以把子问题描述为:求字符串str1<1..m>中从第1个到第i(i <= m)个字符组成的子串str1<1…i>和字符串str2<1..n>中从第1个到第j(j <= n)个字符组成的子串str2<1…j>的最长公共序列,子问题的最长公共序列可以描述为d[i,j] = { Z1,Z2, … Zk },其中Z1-Zk为当前子问题已经匹配到的最长公共子序列的字符。子问题定义以后,还要找出子问题的最优序列d[i,j]的递推关系。分析d[i,j]的递推关系要从str1[i]和str2[j]的关系入手,如果str1[i]和str2[j]相同,则d[i,j]就是d[i-1,j-1]的最长公共序列+1,Zk=str1[i]=str2[j];如果str1[i]和str2[j]不相同,则d[i,j]就是d[i-1,j]的最长公共序列和d[i,j-1]的最长公共序列中较大的那一个。

        最后是确定d[i,j]的边界值,当字符串str1为空或字符串str2为空时,其最长公共子串应该是0,也就是说当i=0或j=0时,d[i,j]就是0。d[i,j]的完整递推关系如下:

 

1.1           反求最长公共子序列

 

         根据1.1得到的最优解子结构递推关系,依次计算i从到m,j从1到n的d[i,j]值,最后得到的d[m,n]就是最长公共子序列的长度。d[m,n]只是最长公共子序列的长度值,表示了两个字符串的相似程度,如果要获得最长公共子序列,就需要在计算出d[m,n]矩阵的值后分析每一步决策的结果,根据每一个最优决策逆向构造出最长公共子序列。为此需要在递推计算d[i,j]的过程中,需要同时记录下最优决策的过程,最优决策的过程用矩阵r表示,r[i,j]表示最长公共子序列的长度值d[i,j]的“递推来源”。根据前面整理的递推关系,如果r[i,j]的值是1,则表示d[i,j]的值由d[i-1,j-1] + 1递推得到;如果r[i,j]的值是2,则表示d[i,j]的值由d[i-1,j]递推得到;如果r[i,j]的值是3,则表示d[i,j]的值由d[i,j-1]递推得到。以字符串“abcdea”和“aebcda”为例,根据递推关系得到的d和r合并到一个矩阵中显示:

图(1)逆向构造最长公共子序列示意图

 

逆向构造最长公共子串的过程从r[m,n]开始,如果r[i,j]=1,则表示两个字符串中的str1[i]和str2[j]相同,可以将str1[i]或str2[j]插入到当前构造的最长公共子序列中。如果r[i,j]≠1,则不改变当前构造的最长公共子序列,但是要根据r[i,j]的值是2还是3,调整r[i,j]倒推的下一个位置。以上述两个字符串构造出的d矩阵和r矩阵为例,逆向构造最长公共子序列的过程如下:r[6,6]=1表示当前公共子串lcs = <str1[6]>(str1[6]和<str2[6]>值一样),同时前一次最优决策来自于d[5,5]。r[5,5]=2表示前一次决策来自于d[4,5],此时r[4,5]=1,表示当前公共子串lcs = < str1[4],str1[6]>,同时前一次最优决策来自于d[3,4]。r[3,4]=1表示当前公共子串lcs = < str1[3],str1[4],str1[6]>,同时前一次最优决策来自于d[2,3]。r[2,3]=1表示当前公共子串lcs = < str1[2],str1[3],str1[4],str1[6]>,同时前一次最优决策来自于d[1,2]。r[1,2]=3表示前一次决策来自于d[1,1],此时r[1,1]=1,表示当前公共子串lcs = < str1[1],str1[2],str1[3],str1[4],str1[6]>。由于r[0,0]是边界,因此逆向构造过程结束,得到最长公共子串的最终结果是lcs = < str1[1],str1[2],str1[3],str1[4],str1[6]>,对应的字符串就是<abcda>。

 

1.1           动态规划算法实现

 

        在编写实现代码时,将每次决策的策略和最优值用一个数据结构描述:

11 typedef struct tagDPLCS

12 {

13    

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吹泡泡的小猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值