当前课程知识点:数据结构(上) > 第一章 绪论(下) > (xc)动态规划 > 01XC-7: LCS:递归
以下我们就参照
Kent Beck所建议的工作流程
暂时把计算效率放在一边
而是首先考虑得到一个
可行的正确的LCS算法
为此我们需要拿起递归
这个强有力的武器
如果把这个问题形式化一下
我们需要考察的是
计算长度为n+1的一个序列A
以及长度为m+1的一个序列B
之间的LCS
那么这无非可以分为三种情况
第0种情况
也是平凡的情况
n或者m等于-1
这个时候 其中之一是空序列
那么我们知道空序列的子序列
只可能是空序列
所以公共子序列
也必然是空序列
所以不妨直接把空序列
也就是长度为零的一个序列返回
这种情况 在我们算法中
应该是扮演着递归基的角色
那么一般的情况呢?
我们说它有两种
首先来看第一种
这两种的区别也就在于
我们要去比较A序列和
B序列的最后那个字符
也就是末字符
如果它们相等
那么这是第一种情况
这时按照这里所说的
我们可以把最后那个字符去掉
大家注意这里是变成了圆括号
表示是开区间
同时把B序列的末字符抹掉
这样得到它们各自长度
减少一个单元的前缀
这对前缀
如果能够递归地求解出LCS的解
那么再并上
最末尾的这个公共的字符
我们说这样就必然构成了
原来那个问题的解
可以用这样的一个图来表示
也就是说 A序列的末字符
与B序列的末字符 都是相同的
在这个时候
可以大胆地做一个裁切
将二者相同的
这个末字符都忽略掉
保存下来
进而递归地求解
它们各自的那个前缀
所构成最长公共子序列
反过来 一旦求到了以后
只要把X缀在其后
就可以得到原来的解
那么 从任务的递归方向来看
我们也可以用这样的一个图来表示
比如说对于这个例子而言
就是这样一种情况
A序列和B序列
分别列在上下方
在这种情况下
它们的末字符相同
对这个例子而言 都是A
这个时候 我们说原问题
当前这个问题的解是多少
完全取决于
它们去掉这个公共字符之后
各自的前缀合在一起
所构成这样的一个递归的问题
这个问题的解
如果能够递归求出来
那么从长度上讲 只要加1
也就是缀上这个公共的字符
就得到了原问题的解
那么这个箭头
表示的是递归的方向
而且这种递归 从策略上讲
其实就是不折不扣的
Decrease and Conquer 减而治之
从图上也可以看出来
相当于把这个问题
切分成了一个平凡的子问题
以及一个规模小于1
但是形式完全一样的问题
当然互补的 还有第二种情况
也就是说 我们经过判断以后发现
当前的序列A和B的末字符并不相等
这个时候呢 从图上看
可以理解为这样
比如说 A的末字符是X
而B的末字符是Y
它们俩并不相等
这个时候说明
末字符这个位置
肯定是不匹配的
那么原问题的解只有两种可能
一种就是 Y这个字符
对整个的公共子序列没有贡献
这个时候 我们不妨把Y切除掉
然后呢 以Y剩下来的那个前缀
和整个的A 去构成一个子任务
继续递归求解
另外呢 也需要
对称的考虑另一种情况
也就是X对整个
公共子序列的匹配没有贡献
这个时候呢 我们也需要
对称的把A的前缀取出来
然后和整个B序列
构成一个新的子任务
递归地求解
这种情况下
整个问题的解
无非这样上、下两种情况
形式化地 我们来描述
可以认为是把A整体的保留下来
把B的末字符抠掉
构成一个子任务 递归求解
或者对称地
把整个B序列保存下来
而把A的末字符抠掉
构成另一个子任务
所以我们可以看到这两个子任务
一旦递归地获得了解
我们最终只要在它们中间
取一个更长的解
就会得到原来问题的解
所以这样一个策略
如果用图来画的话
继续刚才那个例子
我们可以看到末字符当前
一个是C 一个是T 是不等的
这个时候呢
按照刚才的分析
我们应该沿着两个方向
分别地 递归求解
其一 要把B的末字符
也就是T 抹除掉
继续求解
为此 生成的一个递归实例
可以画在上方
另外对称地 我们也可以
把A序列的末字符 抹掉
从而得到另一个子问题
这个递归实例 从构成上讲
只是比原来的A序列
少了这么样一个末字符
从这个图 也可以很清楚地看到
原问题的解 无非是
左边这个子问题
或者上边这个子问题的解
而且确切地讲 应该是
这二者中的最长者
归纳这三种情况
实际上 我们已经得到了一个
完整的递归算法
不是吗?
我们此前给过递归基
然后呢
下面通过一次对末字符的判断
可以进而区分
下面的非平凡的
第一和第二种情况
而且所有的递归都是封闭的
因此我们把这样一段
具体代码实现 这个任务
交给同学们 在课后完成
而我们转而需要分析的是
这个算法的正确性
以及更重要的效率
我们会发现
这个效率并不是很好
-选课之前
--写在选课之前
--宣传片
-考核方式
--考核方式
-OJ系统说明
--关于OJ
--1-注册与登录
--2-界面与选课
--3-提交测试
-关于课程教材与讲义
--课程教材与讲义
-关于讨论区
--关于讨论区
-微信平台
--html
-PA晋级申请
--PA晋级
-(a)计算
--演示
--(a)计算--作业
-(b)计算模型
-(b)计算模型--作业
-(c)大O记号
-(c)大O记号--作业
-(d)算法分析
-(d)算法分析--作业
-(e)迭代与递归
-(e)迭代与递归--作业
-(xc)动态规划
-- 演示
-(xc)动态规划--作业
-本章测验--作业
-(a)接口与实现
--02A-5 复制
-(a)接口与实现--作业
-(b)可扩充向量
-(b)可扩充向量--作业
-(c)无序向量
--02C-1 概述
--02C-3 插入
--02C-6 查找
--02C-8 遍历
-(c)无序向量--作业
-(d1)有序向量:唯一化
-(d1)有序向量:唯一化--作业
-(d2)有序向量:二分查找
-(d2)有序向量:二分查找--作业
-(d3)有序向量:Fibonacci查找
-(d3)有序向量:Fibonacci查找--作业
-(d4)有序向量:二分查找(改进)
-(d4)有序向量:二分查找(改进)--作业
-(d5)有序向量:插值查找
-第二章 向量(下)--(d5)有序向量:插值查找
-(e)起泡排序
--02E-2 改进
--02E-3 反例
-(e)起泡排序--作业
-(f)归并排序
-(f)归并排序--作业
-本章测验--作业
-(a)接口与实现
--03A-4 实现
-(a)接口与实现--作业
-(b)无序列表
--03B-2 查找
-(b)无序列表--作业
-(c)有序列表
--03C-3 查找
-(c)有序列表--作业
-(d)选择排序
--03D-1 构思
--03D-2 实例
--03D-3 实现
--03D-4 推敲
--03D-6 性能
-(d)选择排序--作业
-(e)插入排序
--03E-1 经验
--03E-2 构思
--03E-3 对比
--03E-4 实例
--03E-5 实现
-(e)插入排序--作业
-(xd)习题辅导:LightHouse
-本章测验--作业
- (a)栈接口与实现
--04A-1 栈
--04A-2 实例
--04A-3 实现
- (a)栈接口与实现--作业
-(c1)栈应用:进制转换
-第四章 栈与队列--(c1)栈应用:进制转换
-(c2)栈应用:括号匹配
-(c2)栈应用:括号匹配--作业
-(c3)栈应用:栈混洗
-第四章 栈与队列--(c3)栈应用:栈混洗
-(c4)栈应用:中缀表达式求值
-(c4)栈应用:中缀表达式求值--作业
-(c5)栈应用:逆波兰表达式
-第四章 栈与队列--(c5)栈应用:逆波兰表达式
-(d)队列接口与实现
--04D-1 接口
--04D-2 实例
--04D-3 实现
-第四章 栈与队列--本章测验
-(a)树
--05A-1 动机
--05A-2 应用
-(a)树--作业
-(b)树的表示
--05B-2 父亲
--05B-3 孩子
-第五章 二叉树--(b)树的表示
-(c)二叉树
-(c)二叉树--作业
-(d)二叉树实现
-(d)二叉树实现--作业
-(e1)先序遍历
-(e1)先序遍历--作业
-(e2)中序遍历
-第五章 二叉树--(e2)中序遍历
-(e4)层次遍历
-第五章 二叉树--(e4)层次遍历
-(e5)重构
-(e5)重构--作业
-本章测验--作业
-(a)概述
-(a)概述--作业
-(b1)邻接矩阵
-(b1)邻接矩阵--作业
-(c)广度优先搜索
--06C-2 策略
--06C-3 实现
--06C-5 实例
-(c)广度优先搜索--作业
-(d)深度优先搜索
--06D-1 算法
--06D-2 框架
--06D-3 细节
-(d)深度优先搜索--作业
-第六章 图--本章测验