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

我们之前提到过过动态规划的几个经典问题:

动态规划原理:http://blog.csdn.net/ii1245712564/article/details/45040037

钢条切割问题:http://blog.csdn.net/ii1245712564/article/details/44464689

矩阵链乘法问题:http://blog.csdn.net/ii1245712564/article/details/44464689

今天我们来看一下动态规划的另外一个经典问题:最长公共子序列(longest-common-subsequence)

问题背景

在生物学中,经常需要比较两个不同生物的DNA,一个DNA串由由一串称为碱基的的分子组成,碱基有鸟嘌呤,腺嘌呤,胞嘧啶,胸腺嘧啶四中,我们用英文字母的首字母表示四种碱基,那么DNA就是在有限集{A,C,G,T}上的一个字符串。例如某种生物的DNA序列为:S1=ACCGGTCACGGTCGGAGTGGGAAC…,S2=GTAAAGTCACAAAACGTGT…,我们比较两个DNA串的原因就是希望确定他们的相似度,作为衡量两个物种相似度的标准。如果一个串是另外一个串的子串,那么它们就是相似的,但是这里彼此都不是彼此的子串。于是我们就有了新的衡量标准:寻找一个新串S3,使得S3中的元素都在S1,S2中出现过,且不要求连续,但是碱基在三个串中出现的顺序一定要相同。可以找到的S3的长度越长,表明两个物种越相似!

问题描述

给定两个字符串X[x1,x2,x3,x4,…,xn]和Y[y1,y2,y3,y4,…,ym],求X和Y的最长公共子序列LCS!

问题分析

首先我们尝试一下蛮力法,看看将其中一个序列X[x1,x2,…,xn]里面的所有子串xi提取出来,并与另外一个序列Y[y1,y2,…,ym]进行比较,检测子串xi中的元素是否在Y中按照相同的顺序出现。这里X的子序列一共有2^n个(每一个元素就是加入子串或者不加入子串两种选择,所以共有2*2*2*…2=2^n个),那么在和Y串进行对比的时候,每次都是O(m)。蛮力法运行时间是在指数级别的,要是两个串的长度长一点的话,比较一次都够你睡个午觉了。

那有没有更快的方法呢?动态规划上场。。。

问题解决

首先我们声明定义两种记法,方便后面的书写:我们记X[x1,x2,…,xn]为Xn,比如X2=[x1,x2]了

1.刻画最长公共子序列的特征

令X=[x1,x2,…,xn]和Y=[y1,y2,…,ym],并且他们的最长公共子序列为Z=[z1,z2,…,zk],那么我们有一下结论:

  1. 如果xn == ym, 则xn == ym == zk 且 Zk-1就是Xn-1和Ym-1的最长公共子序列。
  2. 如果xn != ym , 那么当xn != zk 时,Zk是Xn-1和Ym的最长公共子序列。
  3. 如果xn != ym , 那么当ym !=zk时,Zk是Xn和Ym-1的最长公共子序列。

证明:

  1. 在xn == ym时,如果zk != xn 或者zk !=ym,那么X和Y的最长公共子序列为Z+1,与原问题矛盾,所以在xn == ym时, xn == ym == zk成立。如果在xn == ym,Xn-1和Ym-1存在一个比k-1更长的公共子序列W,又因为xm == ym,W+1>Z,与原问题矛盾,不成立!
  2. 如果xn != zk,那么Zk是Xn-1和Ym的最长公共子序列,,如果存在一个长度大于k的序列W是Xn-1和Ym的最长公共子序列,那么W同样也是Xn和Ym的最长公共子序列,W>Z,与原问题矛盾!
  3. 同上可证!

我们看出,LCS问题是具有最优子结构的!

2.一个递归解

由上面得到的最长公共子序列的特征,我们知道,当xn == ym时,最长公共子序列长度就是在子问题算出的最长公共子序列长度上面+1即可,如果 xn != ym,那么最长公共子序列就是[Xn-1,Ym]和[Xn, Ym-1]中最长的哪一个!于是我们等到下面的递归解:

c[i,j]=???0,c[i?1,j?1],max{c[i?1,j],c[i,j?1]}if i==0或者j==0if xn == ymif xn != ym

3.计算LCS长度

通过上面的递归式,我们现在采用两种方法来求解LCS的长度

/** 这里采用自顶向下的算法 */
#include <iostream>
#include <vector>
#include <climits>

using namespace std;

unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData);
/**
 * 计算最长公共子序列长度
 * @param  X X序列
 * @param  Y Y序列
 * @return   最大长度
 */
size_t LCS(std::vector<char> &X , std::vector<char> &Y)
{
    if(X.size() == 0 ||  Y.size() == 0)
        return 0;
    /** 创建一个数组来保存临时数据 */
    std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>());
    for (int i = 0; i <= X.size(); ++i)
    {
        for (int j = 0; j <= Y.size(); ++j)
        {
            if(i == 0 || j == 0)
                tempData[i].push_back(0);
            else
                tempData[i].push_back(UINT_MAX);
        }
    }
    dealLCS(X , Y , tempData);//开始进行计算
    return tempData[X.size()][Y.size()];
}

/**
 * 实际计算LCS的最大长度
 * @param X        X序列
 * @param Y        Y序列
 * @param tempData 缓存数据
 */
unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData)
{
    if(X.size() == 0 || Y.size() == 0 )
        return 0;
    //首先看看这个问题有没有被计算过
    if(tempData[X.size()][Y.size()] != UINT_MAX)
        return tempData[X.size()][Y.size()];
    //表示没有计算过,下面开始计算
    unsigned int maxLength = 0;
    if(X[X.size()-1] == Y[Y.size()-1])// 最后两个元素相等的情况
    {
        maxLength = dealLCS(std::vector<char>(X.begin() , X.end()-1),                            std::vector<char>(Y.begin() , Y.end()-1),                            tempData)+1;
    }
    else//最后两个元素不相等的情况
    {
        unsigned int temp1 = dealLCS(std::vector<char>(X.begin() , X.end()-1),                                     std::vector<char>(Y.begin() , Y.end()) , tempData);
        unsigned int temp2 = dealLCS(std::vector<char>(X.begin() , X.end()),                                     std::vector<char>(Y.begin() , Y.end()-1), tempData);
        maxLength = temp1 > temp2 ? temp1 : temp2;
    }
    tempData[X.size()][Y.size()] = maxLength;
    return maxLength;
}  

int main(int argc, char const *argv[])
{
    char array1[]={‘A‘,‘B‘,‘C‘,‘B‘,‘D‘,‘A‘,‘B‘};
    char array2[]={‘B‘,‘D‘,‘C‘,‘A‘,‘B‘,‘A‘};
    std::vector<char> X(array1 , array1+sizeof(array1));
    std::vector<char> Y(array2 , array2+sizeof(array2));
    cout<<"The Lcs length is :"<<LCS(X,Y)<<endl;
    return 0;
} 

下面这个是自底向上的方法:

/** 这里采用自底向上的算法实现 */

#include <iostream>
#include <vector>
#include <climits>

using namespace std;

unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData);
/**
 * 计算最长公共子序列长度
 * @param  X X序列
 * @param  Y Y序列
 * @return   最大长度
 */
size_t LCS(std::vector<char> &X , std::vector<char> &Y)
{
    if(X.size() == 0 ||  Y.size() == 0)
        return 0;
    /** 创建一个数组来保存临时数据 */
    std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>());
    for (int i = 0; i <= X.size(); ++i)
    {
        for (int j = 0; j <= Y.size(); ++j)
        {
            if(i == 0 || j == 0)
                tempData[i].push_back(0);
            else
                tempData[i].push_back(UINT_MAX);
        }
    }
    dealLCS(X , Y , tempData);//开始进行计算
    return tempData[X.size()][Y.size()];
}

/**
 * 实际计算LCS的最大长度
 * @param X        X序列
 * @param Y        Y序列
 * @param tempData 缓存数据
 */
unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData)
{
    if(X.size() == 0 || Y.size() == 0)
        return 0;
    for (unsigned int i = 0; i < X.size(); ++i)
    {
        for (unsigned int j = 0; j < Y.size(); ++j)
        {
            if(X[i] == Y[j])
            {
                tempData[i+1][j+1] = tempData[i][j]+1;
            }
            else
            {
                unsigned int temp1 = tempData[i+1][j];
                unsigned int temp2 = tempData[i][j+1];
                tempData[i+1][j+1] = temp1 > temp2 ? temp1 : temp2;
            }

        }
    }
    return tempData[X.size()][Y.size()];
}

int main(int argc, char const *argv[])
{
    char array1[]={‘A‘,‘B‘,‘C‘,‘B‘,‘D‘,‘A‘,‘B‘};
    char array2[]={‘B‘,‘D‘,‘C‘,‘A‘,‘B‘,‘A‘};
    std::vector<char> X(array1 , array1+sizeof(array1));
    std::vector<char> Y(array2 , array2+sizeof(array2));
    cout<<"The Lcs length is :"<<LCS(X,Y)<<endl;
    while(1);
    return 0;
} 

如果要了解自上而下和自下而上两种算法的区别,请见:

重叠子问题:http://blog.csdn.net/ii1245712564/article/details/45040037

上面两个算法里面都用到了tempData数组来保存零时数据,tempData[i][j]表示从Xi和Yj的最长公共子串的长度,于是最后我们得到自下而上的tempData矩阵:

tempData[ ][ ]=???????????????00000000001111110011122200122222011222330122333401223344???????????????

最终右下角的tempData[8][7]就是X8和Y7保存的最长公共子串的长度!

4.构造LCS

下面我们以上面的tempData构成的矩阵为基础,从右下角开始,寻找最长的子序列!

自上而下构造LCS

/** 自顶向下带有解决方案 */
#include <iostream>
#include <vector>
#include <climits>

using namespace std;

unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData);
void printSolution(std::vector<char> X ,                   std::vector<std::vector<unsigned int> > & solution ,                   size_t i , size_t j)
{
    if(i == 0 || j ==0)
        return;
    if(solution[i][j] == solution[i-1][j])
        printSolution(X,solution,i-1,j);
    else if(solution[i][j] == solution[i][j-1])
        printSolution(X,solution,i,j-1);
    else if(solution[i][j]-1 == solution[i-1][j-1])
    {
        cout<<i<<" "<<j<<" "<<X[i-1]<<"\n";
        printSolution(X,solution , i-1 ,j-1);
    }
}
/**
 * 计算最长公共子序列长度
 * @param  X X序列
 * @param  Y Y序列
 * @return   最大长度
 */
size_t LCS(std::vector<char> &X , std::vector<char> &Y)
{
    if(X.size() == 0 ||  Y.size() == 0)
        return 0;
    /** 创建一个数组来保存临时数据 */
    std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>());
    for (int i = 0; i <= X.size(); ++i)
    {
        for (int j = 0; j <= Y.size(); ++j)
        {
            if(i == 0 || j == 0)
                tempData[i].push_back(0);
            else
                tempData[i].push_back(UINT_MAX);
        }
    }
    dealLCS(X , Y , tempData);//开始进行计算
    printSolution(X,tempData,X.size(),Y.size());
    return tempData[X.size()][Y.size()];
}

/**
 * 实际计算LCS的最大长度
 * @param X        X序列
 * @param Y        Y序列
 * @param tempData 缓存数据
 */
unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData)
{
    if(X.size() == 0 || Y.size() == 0 )
        return 0;
    //首先看看这个问题有没有被计算过
    if(tempData[X.size()][Y.size()] != UINT_MAX)
        return tempData[X.size()][Y.size()];
    //表示没有计算过,下面开始计算
    unsigned int maxLength = 0;
    if(X[X.size()-1] == Y[Y.size()-1])// 最后两个元素相等的情况
    {
        maxLength = dealLCS(std::vector<char>(X.begin() , X.end()-1),                            std::vector<char>(Y.begin() , Y.end()-1),                            tempData)+1;
    }
    else//最后两个元素不相等的情况
    {
        unsigned int temp1 = dealLCS(std::vector<char>(X.begin() , X.end()-1),                                     std::vector<char>(Y.begin() , Y.end()) , tempData);
        unsigned int temp2 = dealLCS(std::vector<char>(X.begin() , X.end()),                                     std::vector<char>(Y.begin() , Y.end()-1), tempData);
        maxLength = temp1 > temp2 ? temp1 : temp2;
    }
    tempData[X.size()][Y.size()] = maxLength;
    return maxLength;
}  

int main(int argc, char const *argv[])
{
    char array1[]={‘A‘,‘B‘,‘C‘,‘B‘,‘D‘,‘A‘,‘B‘};
    char array2[]={‘B‘,‘D‘,‘C‘,‘A‘,‘B‘,‘A‘};
    std::vector<char> X(array1 , array1+sizeof(array1));
    std::vector<char> Y(array2 , array2+sizeof(array2));
    cout<<"The Lcs length is :"<<LCS(X,Y)<<endl;
    while(1);
    return 0;
} 

自下而上的构造LCS

/** 这里采用自底向上带有绝决方案的算法实现 */
#include <iostream>
#include <vector>
#include <climits>

using namespace std;

unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData);

void printSolution(std::vector<char> X ,                   std::vector<std::vector<unsigned int> > & solution ,                   size_t i , size_t j)
{
    if(i == 0 || j ==0)
        return;
    if(solution[i][j] == solution[i-1][j])
        printSolution(X,solution,i-1,j);
    else if(solution[i][j] == solution[i][j-1])
        printSolution(X,solution,i,j-1);
    else if(solution[i][j]-1 == solution[i-1][j-1])
    {
        cout<<i<<" "<<j<<" "<<X[i-1]<<"\n";
        printSolution(X,solution , i-1 ,j-1);
    }
}
/**
 * 计算最长公共子序列长度
 * @param  X X序列
 * @param  Y Y序列
 * @return   最大长度
 */
size_t LCS(std::vector<char> &X , std::vector<char> &Y)
{
    if(X.size() == 0 ||  Y.size() == 0)
        return 0;
    /** 创建一个数组来保存临时数据 */
    std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>());
    for (int i = 0; i <= X.size(); ++i)
    {
        for (int j = 0; j <= Y.size(); ++j)
        {
            if(i == 0 || j == 0)
                tempData[i].push_back(0);
            else
                tempData[i].push_back(UINT_MAX);
        }
    }
    dealLCS(X , Y , tempData);//开始进行计算
    printSolution(X,tempData,X.size(),Y.size());
    return tempData[X.size()][Y.size()];
}

/**
 * 实际计算LCS的最大长度
 * @param X        X序列
 * @param Y        Y序列
 * @param tempData 缓存数据
 */
unsigned int dealLCS(std::vector<char>  X , std::vector<char>  Y ,
             std::vector<std::vector<unsigned int> > & tempData)
{
    if(X.size() == 0 || Y.size() == 0)
        return 0;
    for (unsigned int i = 0; i < X.size(); ++i)
    {
        for (unsigned int j = 0; j < Y.size(); ++j)
        {
            if(X[i] == Y[j])
            {
                tempData[i+1][j+1] = tempData[i][j]+1;
            }
            else
            {
                unsigned int temp1 = tempData[i+1][j];
                unsigned int temp2 = tempData[i][j+1];
                tempData[i+1][j+1] = temp1 > temp2 ? temp1 : temp2;
            }

        }
    }
    return tempData[X.size()][Y.size()];
}

int main(int argc, char const *argv[])
{
    char array1[]={‘A‘,‘B‘,‘C‘,‘B‘,‘D‘,‘A‘,‘B‘};
    char array2[]={‘B‘,‘D‘,‘C‘,‘A‘,‘B‘,‘A‘};
    std::vector<char> X(array1 , array1+sizeof(array1));
    std::vector<char> Y(array2 , array2+sizeof(array2));
    cout<<"The Lcs length is :"<<LCS(X,Y)<<endl;
    while(1);
    return 0;
} 

上面两种方法都是以tempData矩阵为基础开始的!

时间: 2024-08-09 20:09:24

动态规划之最长公共子序列的相关文章

算法——动态规划篇——最长公共子序列

问题描述      最长公共子序列,英文缩写为LCS(Longest Common Subsequence).其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列.       解决最长公共子序列,一种常用的办法,就是穷举法,组合出所有的情况,但是这样对于长序列的情况来说,是非常不实际.. 假设现在有两个序列,x[]={'A','B','C','B','D','A','B'};y[]={'B','D','C','A'

动态规划解决最长公共子序列问题(转)

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

动态规划解最长公共子序列问题(转)

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

【算法导论之七】动态规划求解最长公共子序列

一.动态规划的概念 动态规划(Dynamic Programming)是通过组合子问题的解而解决整个问题的.分治算法是指将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原始问题的解,与此不同,动态规划适用于子问题不是独立的情况,也就是各个子问题包含公共的子问题.在这种情况下,采用分治法会做许多不必要的工作,即重复地求解公共地子问题.动态规划算法对每个子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案. 动态规划通常应用于最优化问题.此类问

算法导论--动态规划(最长公共子序列)

最长公共子序列问题(LCS) 给定两个序列X=?x1,x2,x3...xm?和Y=?y1,y2,y3...xn?,求X和Y的最长公共子序列. 例如:X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?,的最长公共子序列为?B,C,B,A?,长度为4: 对于此问题,可以采用暴力求解的方式来比对,即穷举出X的所有子序列,用每个子序列与y做一 一比较.假如X序列共有m个元素,对每个元素可以决定选或不选,则X的子序列个数共有2m个,可见与长度m呈指数阶,这种方法效率会很低. 动态规划 前

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

tips : 本文内容是参考了很多著名博客和自己的思考得出的,如有不当欢迎拍砖. 先简单说一下动态规划 通俗地说:动态规划就是将一个可以划分为子问题的问题进行递归求解,不过动态规划将大量的中间结果保存起来, 不管它们是否会用得到,从而在后面的递归求解过程中可以快速求解.由此可以看得出来动态规划是一个以牺牲空间 为代价换取时间的算法. 对于最长公共子序列的题目不用多说,现在来分析一下LCS的动态规划解决思路: 一.首先先观察问题是否符合动态规划最明显的两个特征:最优子结构和重叠子问题 方便起见,以

动态规划求解最长公共子序列

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的.若用分治法来解决这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间.然而,不同子问题的数目常常只有多项式量级.在用分治法求解时,有些子问题被重复计算了许多次.如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,从而得到多项式

九度OJ 1042 Coincidence (动态规划求最长公共子序列)

题目1042:Coincidence 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1689 解决:898 题目描述: Find a longest common subsequence of two strings. 输入: First and second line of each input case contain two strings of lowercase character a-z. There are no spaces before, inside or aft

【动态规划】最长公共子序列问题

题目描述: 给定两个字符串s1s2--sn和t1t2--tn.求出这两个字符串最长的公共子序列的长度.字符串s1s2--sn的子序列指可以表示为si1si2--sim(i1<i2<--<im)的序列.(n>=1,m<=1000) 输入: n=4 m=4 s="abcd" t="becd" 输出: 3 分析: 阶段就是n=0,1,2,3,4--时,m=0,1,2,3--1000时:状态就是对应字符相等还是不相等. 看该题是否符合,每个阶段

闭关修炼 动态规划1——最长公共子序列(UVA111)

经典算法题每日演练——第四题 最长公共子序列 (来自于转载:http://www.cnblogs.com/huangxincheng/archive/2012/11/11/2764625.html) 一: 作用 最长公共子序列的问题常用于解决字符串的相似度,是一个非常实用的算法,作为码农,此算法是我们的必备基本功. 二:概念 举个例子,cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如其中的cb,cgs等等都是其子序列,我们可以看出 子序列不见得一定是连续的,连续的那是子串. 我想