【啃不完的算法导论】- 动态规划 - 最长公共子序列(概念篇)

  以下内容纯是为了熟悉《算法导论》中的内容,高手可略过,其中涉及的书本内容的版权归原作者、译者、出版社所有

  ==================================================================

  求最长公共子序列,一个典型的 动态规划题 和 字符串处理算法,写在这里是希望自己以后能多来看看和改改,温故而知新,有时间的话再加入c/c++代码。

  下面进入正题:

  %% 什么是子序列?概念有点晕,不过拿来学习怎么来精确表达一种内容挺好 %%

  一个给定序列的子序列就是该给定序列中去掉零个或者多个元素。以形式化的方式来说,给定一个序列X=<x1, x2, ..., xm>,另一个序列Z=<z1, z2, ..., zk>是X的一个子序列,如果存在X的一个严格递增下标序列<i1, i2, ..., ik>,使得对所有的j=1, 2, ..., k,有Xij=zj。例如,Z=<B, C, D, B>是X=<A, B, C, B, D, A, B>的一个子序列,相应的下标序列为<2, 3, 5, 7>。

  %% 什么是(最长)公共子序列?概念还是有点晕 %%

  给定两个序列X和Y,称序列Z是X和Y的公共子序列,如果Z既是X的一个子序列又是Y的一个子序列。例如,如果X=<A, B, C, B, D, A, B>,Y=<B, D, C, A, B, A>,则序列<B, C, A>即为X和Y的一个公共子序列。但是序列<B, C, A>不是X和Y的一个最长公共子序列(Longest-Common-Subsequence, LCS),因为它的长度等于3,而同为X和Y的公共子序列<B, C, B, A>其长度等于4。序列<B, C, B, A>是X和Y的一个LCS,序列<B, D, A, B>也是,因为没有长度为5或更大的公共子序列。

  在最长子序列问题中,给定了两个序列X=<x1, x2, ..., xm>和Y=<y1, y2, ..., yn>,希望找出X和Y的最大长度公共子序列。

  步骤1:描述一个最长公共子序列

  %% 枚举方法没试过 %%

  解决LCS问题的一种强力方法是枚举出X的所有子序列,然后逐一检查看其是否为Y的子序列,并随时记录发现的最长子序列。X的每个子序列对应于X的下标集{1, 2, ..., m}的一个子集。X共有2m个子序列,因此这种方法需要指数时间,这对长序列来说是不实际的。

  %% 可以想想“最优子结构性质”是怎么描述的 %%

  然而LCS问题具有最优子结构性质,下面的定理说明了这一点。我们将看到,子问题的自然类对应于两个输入序列的成对“前缀”。(%% 自然类是什么? %%)准确的说,给定一个序列X=<x1, x2, ..., xm>,对i=0, 1, ..., m,定义X的第i个前缀为Xi=<x1, x2, ..., xi>。例如,如果X=<A, B, C, B, D, A, B>,则X4=<A, B, C, B>,而X0空序列。

  %% 最长公共子序列的最优子结构的准确描述 %%

  定理(LCS的最优子结构):

  设X=<x1, x2, ..., xm>和Y=<y1, y2, ..., yn>为两个序列,并设Z=<z1, z2, ..., zk>为X和Y的任意一个LCS。

  %% LCS可能不只有一个,如a,b,c和a,c,b %%

  %% 从xm,yn开始比较,是为了递归定义 %%

  1) 如果xm=yn,那么zk=xm=yn而且Zk-1是Xm-1和Yn-1的一个LCS。

  2) 如果xm≠yn,那么zk≠xm蕴含Z是Xm-1和Y的一个LCS。

  3) 如果xm≠yn,那么zk≠yn蕴含Z是X和Yn-1的一个LCS。

  1,2,3证明略。 %% 证明是《算法导论》里比较繁琐的一块内容,不过仔细看看过程很精彩 %%

  步骤2:一个递归解

  %% 重叠子问题在动态规划中很常见 %%

  由上述定理可以知道,在找X=<x1, x2, ..., xm>和Y=<y1, y2, ..., yn>的一个LCS时,可能要检查一个或两个子问题。如果xm=yn,必须找出Xm-1和Yn-1的一个LCS。将xm=yn添加到这个LCS上,可以产生X和Y的一个LCS。如果xm≠yn,就必须解决两个子问题:找出Xm-1和Y的一个LCS,以及找出X和Yn-1的一个LCS。这两个LCS中,较长的就是X和Y的一个LCS,因为这些情况涉及了所有的可能,其中一个最优的子问题解必须被使用在X和Y的一个LCS中。

  可以很容易地看出LCS问题中的重叠子问题性质。为找出X和Y的一个LCS,可能需要找出X和Yn-1的一个LCS以及Xm-1和Y的一个LCS。但这两个子问题都包含着找Xm-1和Yn-1的一个LCS的子子问题。还有许多其他的子问题共享子子问题。

  %% 递归式看着容易理解,要自己写的时候老是记不起来 %%

  像在矩阵链乘法问题中一样,LCS问题的递归解涉及到建立一个最优解的值的递归式。定义c[i, j]为序列Xi和Yi的一个LCS长度。如果i=0或j=0,其中一个的序列长度为0,因此LCS的长度为0。由LCS问题的最优子结构可得递归式

  c[i, j] = 0              如果 i=0 或 j=0

     = c[i-1, j-1] + 1       如果 i, j>0 和 xi=yi  %% 第i个字符满足条件,加到子序列中,长度加1 %%

     = max( c[i, j-1], c[i-1, j] )  如果 i, j>0 和 xi≠yi  %% 第i个字符不一样了,根据前面的计算结果来比较哪个长一点,再取值%%

  %% 下面的这个“子问题因为原问题的条件而被排除”没怎么明白,是条件不同考虑的子问题不同? %%

  观察这个递归公式,问题的一个条件限制了我们可能考虑的子问题。当xi=yi时,可以而且应该考虑寻找Xi-1和Yi-1的LCS的子问题。否则,应另外考虑寻找Xi和Yj-1以及Xi-1和Yj的LCS的两个子问题。在前面已经讨论的动态规划算法(装配线调度和矩阵链乘法)中,没有任何子问题因为原问题的条件而被排除。寻找LCS不是唯一的因为问题的条件而排除子问题的动态规划算法。例如,编辑距离也具有这个特征。

  步骤3:计算LCS的长度

  %% 自底向上的方法,一般是多重循环 %%

  根据公式,可以很容易地写出一个指数时间的递归算法,来计算两个序列的LCS的长度。因为只有Θ(mn)个不同的子问题,所以可以用动态规划来自底向上计算解。

  %% c表用来记录LCS的长度,b表用来跟踪当前选择的最优解,方便结束时构造出来LCS %%

  过程LCS-LENGTH以两个序列X=<x1, x2, ..., xm>和Y=<y1, y2, ..., yn>为输入。它把c[i, j]值填入一个按行计算表项的表c[0..m, 0..n]中。(也就是,c的第一行从左到右填入,然后开始第二行,等等)。它还维护表b[0..m, 0..n]以简化最优解的构造。从直觉上看,b[i, j]指向一个表项,对应于在计算c[i, j]时所选择的最优子问题的解。该程序返回表b和c;c[m, n]包含X和Y的一个LCS的长度。

  %% 有了递归式,代码就简单了,说明先定义递归式有多重要 %%

  伪代码如下:

  

View Code
 1 //LCS-LENGTH(X, Y)
 2 //
 3 // m <- length[X]
 4 // n <- length[Y]
 5 // 构造边界
 6 // for i <- 1 to m
 7 //     do c[i, 0] <- 0
 8 // for j <- 0 to n
 9 //     do c[0, j] <- 0
10 // 自底向上,选择最优结果
11 // for i <- 1 to m
12 //     do for j <- 1 to n
13 //         不同条件,不同选择
14 //         do if xi = yj
15 //                 then c[i, j] <- c[i-1, j-1] + 1
16 //                         b[i, j] <- left-up //指向左上方
17 //                 else if c[i-1, j] >= c[i, j-1]
18 //                         then c[i, j] <- c[i-1, j]
19 //                                 b[i, j] <- up //指向上方
20 //                         else c[i, j] <- c[i, j-1]
21 //                                 b[i, j] <- left //指向左边
22 // return c and b

 

  下图给出了在序列X=<A, B, C, B, D, A, B>和Y=<B, D, C, A, B, A>上,由LCS-LENGTH计算出的表。这个程序的运行时间为O(mn),因为每个表项的计算时间为O(1)。

  

  步骤4:构造一个LCS

  %% 箭头表示方法很形象 %%

  有LCS-LENGTH返回的表b可以被用来快速构造X=<x1, x2, ..., xm>和Y=<y1, y2, ..., yn>的一个LCS。首先从b[m, n]处开始,沿着箭头在表格中跟踪下去。每当在表项b[i, j]中遇到left-up时,即意味着xi=yj是LCS的一个元素。这种方法是按照反序来找LCS的每一个元素的。下面的递归过程按正常的前序输出X和Y的一个LCS。初始调用为PRINT-LCS(b, X, length[X], length[Y])。

  %% 这个递归打印结果也很好 %%

  伪代码如下:

  

View Code
 1 // PRINT-LCS(b, X, i, j)
 2 // 
 3 // if i=0 or j=0
 4 //     then return
 5 // if b[i, j] = left-up //指向左上方
 6 //     then PRINT-LCS(b, X, i-1, j-1)
 7 //         print xi
 8 // else if b[i, j] = up //指向上方
 9 //     then PRINT-LCS(b, X, i-1, j)
10 // else PRINT-LCS(b, X, i, j-1) //指向左边

 

  对图中的表b,此程序输出“B C B A”。因为在递归的每个阶段i和j至少有一个要减小,故该过程的运行时间为O(m+n)。

  改进代码

  一旦设计出某个算法之后,常常可以在时间或者空间上对该算法做些改进。对直观的动态规划算法来说尤其如此,有些改变可以简化代码并改进一些常数因子,但并不会带来算法性能方面的渐进改善。其他一些改变则可以在时间和空间上有相当大的渐进节省。

  %% 完全去掉表b(箭头表)没试过这种方法。 %%

  例如,我们可以完全去掉表b。每个表项c[i, j]仅依赖于另外三个c表项:c[i-1, j-1],c[i-1, j]和c[i, j-1]。给定c[i, j]的值,我们可以在O(1)时间内确定这三个值中的哪一个被用来计算c[i, j]的,而不检查表b。这样,利用一个类似于PRINT-LCS的过程,在O(m+n)时间内即可重构一个LCS。(练习中要求伪代码)。虽然用这种方法节省了Θ(mn)空间,但计算一个LCS时所需要的辅助空间并没有渐进地减少,因为表c总是需要占据Θ(mn)空间的。

  %% 渐进空间需求? 计算长度的话,表c实际上是只有两行有用,也没试过这种方法,不过要构造LCS还是需要一定的辅助信息 %%

  然而,我们能减少LCS-LENGTH的渐进空间需求,因为它一次只需表c的两行:正在被计算的一行和前面一行(实际上,仅需略多于表c一行的空间就可以计算一个LCS的长度。)。如果仅要求出一个LCS的长度,则这种改进是有用的;如果要重构一个LCS的元素,则小的表无法包含足够的信息来使我们在O(m+n)时间内重新执行以前各步。

 

转载于:https://www.cnblogs.com/futuredo/archive/2012/10/14/2723279.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/434199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python发送邮箱_你知道怎么用Python发送邮件吗?

作者 | 陈熹来源 | 早起Python(ID&#xff1a;zaoqi-python)头图 | CSDN 下载自东方IC前言本文主要对邮件操作基础知识及代码进行介绍&#xff0c;用Python发邮件有哪些优势&#xff1f;批量发送邮件&#xff0c;并且各邮件可以不同高度自定义的定时发送更有效地管理收件箱基本…

解决 avformat_alloc_context无法识别的问题

由于最近库更新&#xff0c;如果还是用原来的测试用例会碰到avformat_alloc_context 无法识别的问题 avformat_alloc_context is cannot indentified。 解决办法就是将 ocavformat_alloc_context 改成 ocav_alloc_format_context(); 就ok啦 注:我用的库是FFmpeg-full-SD…

python去空格的函数_Python怎么去掉最后的空格

strip()函数 去空格nrt函数的用法 strip 同时去掉左右两边的空格&#xff08;推荐学习&#xff1a;Python视频教程&#xff09; lstrip 去掉左边的空格 rstrip 去掉右边的空格 具体示例如下&#xff1a;>>>a" hello world&#xff01;&#xff01; " >&g…

Java中getResource()的用法

用JAVA获取文件&#xff0c;听似简单&#xff0c;但对于很多像我这样的新人来说&#xff0c;还是掌握颇浅&#xff0c;用起来感觉颇深&#xff0c;大常最经常用的&#xff0c;就是用JAVA的File类&#xff0c;如要 取得c:/test.txt文件&#xff0c;就会这样用File file new Fil…

centos中安装mysql5.6_CentOS中安装MySQL5.6报错的解决方法

由于项目需要&#xff0c;必须使用最新出来的MySQL5.6社区版本&#xff0c;使用的操作系统是CentOS6.3。然后安装到中途报错如下&#xff1a;file /usr/share/mys由于项目需要&#xff0c;必须使用最新出来的MySQL5.6社区版本&#xff0c;使用的操作系统是CentOS6.3。首先&…

python逻辑运算符不懂_Python之逻辑运算符

这一小节我在动笔之前犹豫到底要不要动手写&#xff0c;虽然简单但是防止遗忘&#xff0c;博主还是决定记录一下。Python中运算符主要分为算术运算符&#xff0c;赋值运算符&#xff0c;比较运算符&#xff0c;逻辑运算符以及成员运算符。下面详细记录这5种运算符。 1. 算术运算…

使用Wireshark进行SIP包解析

1. 安装Wireshark 下载Wireshark后&#xff0c;安装很简单&#xff0c;基本上只需要点击“Next”和“I agree”等按钮&#xff0c;不再赘述。 2. Wireshark介绍 参见&#xff1a;http://man.lupaworld.com/content/network/wireshark/Introduction.html copy一下简要介绍&a…

Android-Animations的使用大全之二:Frame Animation和其他

Android-Animations的使用大全之一&#xff1a;Tweened Animations详解 5 Frame-By-Frame Animations的使用方 1 在res/drawable中创建一个xml文件&#xff0c;定义Animation的动画播放序列 anim_nv.xml Xml代码 <animation-list xmlns:android"http://schemas.androi…

wireshark分析SIP协议——注册

SIP 是VOIP目前非常流行的一种协议。有关协议的详细原理参照相关文档。本文通过wireshark抓包分析SIP user agent&#xff08;用户代理客户机&#xff0c;uac&#xff09;与SIPserve之间的交互过程&#xff0c;在拨打SIP电话之前&#xff0c;先需要搭建相应的环境&#xff1a;根…

micropython仿真器_Micropython教程之TPYBoard DIY超声波测距仪实例演示

1.实验目的 1. 学习在PC机系统中扩展简单I/O?接口的方法。 2. 进一步学习编制数据输出程序的设计方法。 3. 学习超声波模块的测距原理。 4. 学习LCD5110接线方法 5. 学习TPYboard控制超声波模块测距。(萝卜学科编程教育tpyboard。com) 2.所需元器件 超声波模块一个 TPYBoard板…

monotouch在ipad中的实例应用--显示图像和文字

本节主要讲述在苹果环境中使用monodevelop开发ipad的一个实例&#xff0c;具体操作如下 1、新建项目 选择monotouch--ipad--Empty project 命名为iPad01 2、添加新文件&#xff0c;选择monotouch--ipad view&#xff0c;命名为showView&#xff0c;这样会在项目中多出来三个文件…

mysql的util_JDBC连接mysql工具类Util供大家参考

> list new ArrayList>();//静态代码块&#xff0c;在程序编译的时候执行static {//创建Properties对象Properties properties new Properties();//获取文件输入流InputStream is JDBCUtil_cj.class.getClassLoader().getResourceAsStream("jdbc.properties"…

打开pjsip2.1版本的视频支持

要打开pjsip2.1的视频支持。 首先需要修改pjmedia\include\pjmedia\config.h文件&#xff0c; 如下所示&#xff0c;修改PJMEDIA_HAS_VIDEO的值为1, 修改PJMEDIA_HAS_FFMPEG的值为1 #ifndef PJMEDIA_HAS_VIDEO # define PJMEDIA_HAS_VIDEO 1 #endif #ifndef…

MySQL数据库在众多表中对表名的查询及预处理存储过程(变量做表名)

以下的文章主要介绍的是MySQL数据库在众多表中进行表名与字段名的查询的实际操作步骤&#xff0c;以及对实现其查询所要用到的SQL 语句的介绍&#xff0c;还有两个实际解决方案的描述&#xff0c;以下就是文章的主要内容描述。 在MySQL 众多表中查找一个表名或者字段名的 SQL 语…

MTU MSS 详解记录

先学习理解一下帧的封装格式&#xff1a; 需要注意的是&#xff0c;区别两种帧封装格式&#xff1a;802标准帧和以太网帧1&#xff0c;在802标准定义的帧格式中&#xff0c;长度字段是指它后续数据的字节长度&#xff0c;但不包括C R C检验码。RFC 1042&#xff08;IEEE 802&a…

MySQL字符集的基本类型与统一字符集

以下的文章主要介绍的是MySQL字符集的基本类型&#xff0c; 统一字符集的实际操作方法&#xff0c;等相关内容的介绍&#xff0c;以下就是MySQL字符集的相关内容的描述&#xff0c;希望你会在以后的学习或是工作中带来很大的帮助。 一. MySQL字符集类型 MySQL服务器中有六个关键…

python socket发包_一个python发包的脚本

#codingutf-8Created on 2016年4月12日author: administraterfrom socket import *import timeHOST 172.16.6.70PORT 7125BUFSIZ 1024ADDR (HOST, PORT)def gatewatTcpClient():sock socket(AF_INET,SOCK_STREAM)sock.connect(ADDR)register_data 7e01004026018691830270…

通达oa 不允许从该ip登陆_通达OA-命令执行漏洞复现

通达OA-命令执行一、环境安装文件&#xff1a;链接:https://pan.baidu.com/s/1Y78Zs-7Igi4MRE0J_Dp-dQ 提取码:2b3i二、漏洞验证任意文件上传漏洞 /ispirit/im/upload.php本地文件包含漏洞 /ispirit/interface/gateway.php这两个路径不需要登录认证。burp抓包修改数据包上传文件…

mysql数据转存到时序数据库_干货丨如何高速迁移MySQL数据到时序数据库DolphinDB...

DolphinDB提供了两种导入MySQL数据的方法&#xff1a;ODBC插件和MySQL插件。我们推荐使用MySQL插件导入MySQL数据&#xff0c;因为它的速度比ODBC导入更快&#xff0c;导入6.5G数据&#xff0c;MySQL插件的速度是ODBC插件的4倍&#xff0c;并且使用MySQL插件无需任何配置&#…

优秀程序员的45个习惯

摘要&#xff1a;值得打印出来贴在办公室墙上学习实践的箴言。 优秀来自好的习惯。怎样成为优秀的开发人员&#xff1f;图灵公司最近热销的《高效程序员的45个习惯》一书给出了很好的解答&#xff0c;非常值得一读。 这本书的英文原版荣获了有软件奥斯卡之称的Jolt生产效率大奖…