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

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

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

  求最长公共子序列,一个典型的 动态规划题 和 字符串处理算法,写在这里是希望自己以后能多来看看和改改,温故而知新,有时间的话再加入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;并且各邮件可以不同高度自定义的定时发送更有效地管理收件箱基本…

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

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

使用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;这样会在项目中多出来三个文件…

MTU MSS 详解记录

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

通达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生产效率大奖…

java 类型转换_java中的基本数据类型的转换

本文参考了如下两篇文章:Java中&#xff0c;经常可以遇到类型转换的场景&#xff0c;从变量的定义到复制、数值变量的计算到方法的参数传递、基类与派生类间的造型等&#xff0c;随处可见类型转换的身影。Java中的类型转换在Java编码中具有重要的作用。首先&#xff0c;来了解下…

Activity的四种加载模式(转载)

在多Activity开发中&#xff0c;有可能是自己应用之间的Activity跳转&#xff0c;或者夹带其他应用的可复用Activity。可能会希望跳转到原来某个Activity实例&#xff0c;而不是产生大量重复的Activity。这需要为Activity配置特定的加载模式&#xff0c;而不是使用默认的加载模…

centos 安装tomcat_简单介绍Linux配置mysql,tomcat,Nginx 开机自启动的几个方式

概述一般我们打算把一些服务&#xff0c;例如 mysql&#xff0c;tomcat&#xff0c;Nginx设置开机自启动的话一般是有三四种方式来实现&#xff0c;下面以mysql&#xff0c;tomcat&#xff0c;Nginx做例子来演示。一、使用定时任务 cron 命令创建定时任务来运行 .sh 脚本。在sh…

java 永久代_Java新生代、老生代和永久代详解

前言: 还是面试经常被q&#xff0c;小结一下image.pngJVM中的堆一般分为三部分&#xff0c;新生代、老年代和永久代。1 新生代主要是用来存放新生的对象。一般占据堆空间的1/3&#xff0c;由于频繁创建对象&#xff0c;所以新生代会频繁触发MinorGC进行垃圾回收。新生代分为Ede…

java算法:冒泡排序

java算法&#xff1a;冒泡排序 冒泡排序&#xff1a;不断遍历文件&#xff0c;交换倒序的相邻元素&#xff0c;直到文件排好顺序。冒泡排序的主要优点是容易实现&#xff0c;冒泡排序通常会比选择排序、插入排序慢。 如&#xff0c;对EXAMPLE 字母进行排序&#xff1a; E X…

strlwr,strupr函数

函数原型&#xff1a;extern char *strlwr(char *str) extern char *strupr(char *s) 参数说明&#xff1a;str为要转换的字符串。 所在库名&#xff1a;#include <string.h> 函数功能&#xff1a;将字符串str中的大(小)写字母转换成为小(大)写字母&#xff…

python中的类怎样理解_理解Python数据类:Dataclass fields 的概述(下)

原标题 Understanding Python Dataclasses?—?Part 2 &#xff0c;作者为 Shikhar Chauhan 。这是 Python 最新的 Dataclasses 系列的第二部分内容。在第一部分里&#xff0c;我介绍了 dataclasses 的一般用法。这篇主要介绍另一个特征&#xff1a;dataclasses.field。我们已…

西部数码域名解析到阿里云_西部数码云主机好吗 稳定性如何

随着云计算技术的不断发展&#xff0c;我国云计算市场的经济效益也在日渐扩大&#xff0c;各个云服务商之间的竞争异常激烈。在这种环境下&#xff0c;备受人们关注的云服务企业主要呈现两大类&#xff0c;一是互联网巨头背景的阿里云、腾讯云等&#xff1b;二是以西部数码为代…

mysql8.0.22安装步骤图解_MySQL server 5.5的安装 步骤图解

作者&#xff1a;极客小俊 一个专注于web技术的80后我不用拼过聪明人&#xff0c;我只需要拼过那些懒人 我就一定会超越大部分人!知乎极客小俊&#xff0c;官方首发原创文章还有人用老版本的mysql 5.5吗&#xff1f;&#xff1f; 如果有的话 并且不会安装的小白看下面的步骤图吧…