第十五章 动态规划——最长公共子序列

1、基本概念

  一个给定序列的子序列就是该给定序列中去掉零个或者多个元素的序列。形式化来讲就是:给定一个序列X={x1,x2,……,xm},另外一个序列Z={z1、z2、……,zk},如果存在X的一个严格递增小标序列<i1,i2……,ik>,使得对所有j=1,2,……k,有xij = zj,则Z是X的子序列。例如: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的一个公共子序列,但不不是最长公共子序列。

最长公共子序列(LCS)问题描述:给定两个序列X={x1,x2,……,xm}和Y={y1,y2,……,yn},找出X和Y的最长公共子序列。

2、动态规划解决过程

1)描述一个最长公共子序列

  如果序列比较短,可以采用蛮力法枚举出X的所有子序列,然后检查是否是Y的子序列,并记录所发现的最长子序列。如果序列比较长,这种方法需要指数级时间,不切实际。

  LCS的最优子结构定理:设X={x1,x2,……,xm}和Y={y1,y2,……,yn}为两个序列,并设Z={z1、z2、……,zk}为X和Y的任意一个LCS,则:

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

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

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

  定理说明两个序列的一个LCS也包含两个序列的前缀的一个LCS,即LCS问题具有最优子结构性质。

2)一个递归解

  根据LCS的子结构可知,要找序列X和Y的LCS,根据xm与yn是否相等进行判断的,如果xm=yn则产生一个子问题,否则产生两个子问题。设C[i,j]为序列Xi和Yj的一个LCS的长度。如果i=0或者j=0,即一个序列的长度为0,则LCS的长度为0。LCS问题的最优子结构的递归式如下所示:

3)计算LCS的长度

  采用动态规划自底向上计算解。书中给出了求解过程LCS_LENGTH,以两个序列为输入。将计算序列的长度保存到一个二维数组C[M][N]中,另外引入一个二维数组B[M][N]用来保存最优解的构造过程。M和N分别表示两个序列的长度。该过程的伪代码如下所示:

LCS_LENGTH(X,Y)
    m = length(X);
    n = length(Y);
    for i = 1 to m
      c[i][0] = 0;
    for j=1 to n
      c[0][j] = 0;
    for i=1 to m
       for j=1 to n
           if x[i] = y[j]
              then c[i][j] = c[i-1][j-1]+1;
                   b[i][j] = '';
           else if c[i-1][j] >= c[i][j-1]
                  then c[i][j] = c[i-1][j];
                       b[i][j] = '↑';
                  else
                       c[i][j] = c[i][j-1];
                       b[i][j] = '←';
return c and b

由伪代码可以看出LCS_LENGTH运行时间为O(mn)。

4)构造一个LCS

  根据第三步中保存的表b构建一个LCS序列。从b[m][n]开始,当遇到'\'时,表示xi=yj,是LCS中的一个元素。通过递归即可求出LCS的序列元素。书中给出了伪代码如下所示:

PRINT_LCS(b,X,i,j)
    if i==0 or j==0
        then return
    if b[i][j] == '\'
        then PRINT_LCS(b,X,i-1,j-1)
             print X[i]
     else if b[i][j] == '|'
                then PRINT_LCS(b,X,i-1,j)
             else PRINT_LSC(b,X,i,j-1)

3、编程实现

  现在采用C++语言实现上述过程,例如有两个序列X={A,B,C,B,D,A,B}和Y={B,D,C,A,B,A},求其最长公共子序列Z。完整程序如下所示:

#include<iostream>
#include<string>
#include<cstring>
using namespace std;

#define X_LEN  7
#define Y_LEN  6
string s1="ABCBDAB";
string s2="BDCABA";
int c[X_LEN+1][Y_LEN+1];
char b[X_LEN+1][Y_LEN+1];

void lcs_length();
void print_matrix();
void print_lcs(char b[X_LEN+1][Y_LEN+1],string s,int i,int j);

int main()
{
    memset(b,0,sizeof(b));
    lcs_length();
    print_matrix();
    cout<<"打印子序列:"<<endl;
    print_lcs(b,s1,X_LEN,Y_LEN);
    return 0;
}

void lcs_length()
{
    int i,j;
    for(i=1;i<=X_LEN;i++)
        c[i][0]=0;
    for(i=0;i<=Y_LEN;i++)
        c[0][i]=0;
    for(i=1;i<=X_LEN;i++)
        for(j=1;j<=Y_LEN;j++)
    {
        if(s1[i-1]==s2[j-1])
        {
            c[i][j]=c[i-1][j-1]+1;
            b[i][j]='\\';
        }
        else if(c[i-1][j]>=c[i][j-1])
        {
            c[i][j]=c[i-1][j];
            b[i][j]='|';
        }
        else
        {
            c[i][j]=c[i][j-1];
            b[i][j]='-';
        }
    }
}

void print_matrix()
{
    int i,j;
    cout<<"长度矩阵:"<<endl;
    for(i=0;i<=X_LEN;i++)
    {
        for(j=0;j<=Y_LEN;j++)
            cout<<c[i][j]<<" ";
        cout<<endl;
    }
    cout<<"方向矩阵:"<<endl;
    for(i=0;i<=X_LEN;i++)
    {
        for(j=0;j<=Y_LEN;j++)
            cout<<b[i][j]<<" ";
        cout<<endl;
    }
}

void print_lcs(char b[X_LEN+1][Y_LEN+1],string s,int i,int j)
{
    if(i==0||j==0)
        return;
    if(b[i][j]=='\\')
    {
        print_lcs(b,s,i-1,j-1);
        cout<<s[i-1]<<" ";
    }
    else if(b[i][j]=='|')
    {
        print_lcs(b,s,i-1,j);
    }
    else
        print_lcs(b,s,i,j-1);

}

运行结果:

可以参考:http://www.cnblogs.com/kaituorensheng/archive/2013/03/31/2992319.html

时间: 2024-10-08 03:31:22

第十五章 动态规划——最长公共子序列的相关文章

第十五章 动态规划——钢条切割

前言:动态规划的概念 动态规划(dynamic programming)是通过组合子问题的解而解决整个问题的.分治算法是指将问题划分为一些独立的子问题,递归的求解各个问题,然后合并子问题的解而得到原问题的解.例如归并排序,快速排序都是采用分治算法思想.本书在第二章介绍归并排序时,详细介绍了分治算法的操作步骤,详细的内容请参考:http://www.cnblogs.com/Anker/archive/2013/01/22/2871042.html.而动态规划与此不同,适用于子问题不是独立的情况,也

第十五章 动态规划——最优二叉搜索树

1.前言: 接着学习动态规划方法,最优二叉查找树问题.二叉查找树参考http://www.cnblogs.com/Anker/archive/2013/01/28/2880581.html.如果在二叉树中查找元素不考虑概率及查找不成功的情况下,可以采用红黑树或者平衡二叉树来搜索,这样可以在O(lgn)时间内完成.而现实生活中,查找的关键字是有一定的概率的,就是说有的关键字可能经常被搜索,而有的很少被搜索,而且搜索的关键字可能不存在,为此需要根据关键字出现的概率构建一个二叉树.比如中文输入法字库中

第十五章 动态规划——矩阵链乘法

前言:今天接着学习动态规划算法,学习如何用动态规划来分析解决矩阵链乘问题.首先回顾一下矩阵乘法运算法,并给出C++语言实现过程.然后采用动态规划算法分析矩阵链乘问题并给出C语言实现过程. 1.矩阵乘法       从定义可以看出:只有当矩阵A的列数与矩阵B的行数相等时A×B才有意义.一个m×r的矩阵A左乘一个r×n的矩阵B,会得到一个m×n的矩阵C.在计算机中,一个矩阵说穿了就是一个二维数组.一个m行r列的矩阵可以乘以一个r行n列的矩阵,得到的结果是一个m行n列的矩阵,其中的第i行第j列位置上的

算法知识之最长公共子序列问题(动态规划)

最近朋友让帮做个关于动态规划的最长公共子序列的问题,翻看以前的笔记并完成该题后,顺便写这样一篇文章,希望对大家有所帮助,同时也帮助自己回顾该知识点. 一.最长公共子序列的定义 子序列:若给定序列X={x1,x2,-,xm},则另一序列Z={z1,z2,-,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,-,ik}使得对于所有j=1,2,-,k有:zj=xij.公共子序列:给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列.最长公共子序列:给

第十五章 接口[《.net框架程序设计》读书笔记]

.net框架|笔记|程序|设计 第十五章 接口 摘要: 接口的应用及完全限定名方式定义接口的应用. 一. 接口与继承 l C#支持单实现继承和多接口继承 l 接口中可以定义:事件.无参属性(属性).含参属性(索引器):C#不允许接口定义任何静态成员(CLR却允许定义静态成员):CLR不允许接口定义实例字段和构造器. l 缺省为public abstract 方法,但不可用任何修饰符进行修饰(包括public) l 将值类型转换为接口类型(假设其实现了某个接口),则值类型被装箱为引用类型,以调用其

第十五章之(三)RTTI

前言:刚找到新工作,这两天忙着搬家,添加一些日用品,好方便日后使用,浪费了蛮多时间的.所以到现在才补上这一章节. 之前跳过了RTTI,去看下一部分的类型转换运算符,被dynamic_cast搞的很头晕(主要是因为没搞懂为什么用这个),只能回头补上这一节,这时才发现,这一节是不能跳过的. 另外,这小节说实话,蛮难理解的(主要是指其原理,和为什么要有dynamic_cast这个类型转换运算符),这里简单的来概括,就是让指针只有在能安全使用转换后的类方法的情况下,才会被强制转换. ----------

动态规划之最长公共子序列

给定两个序列x和y,称z是x和y的公共子序列,如果z既是x的子序列,又是y的子序列:最长的公共子序列称作最长公共子序列LCS(longest common subsequence). 解题思路 (1)LCS的最优子结构 设zk是xm和yn的一个LCS,则,如果x和y的最后一个元素相同,则z中去掉最后一个元素之后zk-1仍为xm-1和yn-1的LCS. 如果xm!=yn,若zk!=xm,则z是xm-1和y的一个LCS,若zk!=yn,则z是xm和yn-1的LCS. (2)一个递归解 设c[i][j

动态规划解最长公共子序列(LCS)问题 (附可打印LCS完整代码)

一.动态规划法 经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题.简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加. 为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法. 二.问题:求两字符序列的最长公共字符子序列(LCS) 问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的

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

最长公共子序列也称作最长公共子串,英文缩写是LCS(Longest Common Subsequence).其定义 是:一个序列S,如果分别是两个或多个已知序列的子序列,且是符合此条件的子序列中最长的,则称 S为已知序列的最长公共子序列. 关于子序列的定义通常有两种方式,一种是对子序列没有连 续的要求,其子序列的定义就是原序列中删除若干元素后得到的序列.另一种是对子序列有连续的要 求,其子序列的定义是原序列中连续出现的若干个元素组成的序列.求解子序列是非连续的最长公共 子序列问题是一个十分实用的