算法课上老师留的作业,最长公共子序列LCS(Longest Common Subsequence)问题,首先看到这个问题感觉有点复杂,和最长公共子串不同,公共子序列并不要求元素相邻,看起来只有穷举才能将所有可能全部罗列,但是伟大的数学家早已为此提供了更优的解决方案——动态规划。

  一、问题描述与分析

    子序列的定义:一个给定序列的子序列是在该序列中删去若干元素后得到的序列。

    而最长公共子序列的定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。

    若采用穷举搜索法,即对序列X的每个子序列检查是否也是序列Y的子序列,并在检查中选出最长的公共子序列,若X有m个元素,Y有n个元素,则X有2^m个子序列,算法的时间复杂度为O(2^(m+n))。

  那么如果使用动态规划方法来解决这个问题呢?

  首先:什么样的问题适合用dp来解呢?能采用动态规划求解的问题的一般要具有3个性质:

   (1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

  (2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。

   (3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

  所有求解dp问题的基本步骤有哪些呢?

求解的基本步骤

动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。

初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。

(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。

(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。

实际应用中可以按以下几个简化的步骤进行设计:

(1)分析最优解的性质,并刻画其结构特征。

(2)递归的定义最优解。

(3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值

(4)根据计算最优值时得到的信息,构造问题的最优解

  建立一个c[i,j]来记录X和Yj的最长公共子序列的长度。

    当i=0或j=0时,c[i,j]=0(X和Y为空子序列);

    当i,j>0且Xi =Y时,c[i,j]=c[i-1,j-1]+1;

    当i,j>0且Xi ≠Y时,c[i,j]=max(c[i,j-1],c[i-1,j])。

  二、求解算法

    1、计算最优值

      按以上关系可以轻松的写出求解c[i,j]的算法,X、Y序列为输入,二维数组c[i][j]为输出

 1 void LCS_length(const string x,const string y){
 2     const int m = x.size();
 3     const int n = y.size();
 4     int c[100][100];                //生成二维数组c
 5     for(int i = 0;i <= m; ++i)      //i =0 和j = 0时c = 0
 6         c[i][0] = 0;
 7     for(int j = 1;j <= n; ++j)
 8         c[0][j] = 0;
 9     for(int i = 1;i <= m; ++i){         //x[i] = y[i]时 c[i][j] =c[i-1][j-1]+1
10         for(int j = 1; j<=n; ++j){      //x[i]不等于y[j]时c[i][j] = max(c[i,j-1],c[i-1,j]
11             if(x[i-1] == y[j-1]){
12                 c[i][j] = c[i-1][j-1]+1;
13             }
14             else if(c[i-1][j] >= c[i][j-1]){
15                 c[i][j] = c[i-1][j];
16             }
17             else{
18                 c[i][j] = c[i][j-1];
19             }
20         }
21     }
22     for(int i = 0;i <= m; ++i){       //输出二维数组c
23         for(int j = 0; j<=n; ++j){
24             cout<<c[i][j]<<" ";
25         }
26         cout<<'\n';
27     }
28 }
29 int LCS(const string x,const string y,int i,int j){ //递归求最优值
30     if(i==0||j==0)
31         return 0;                                   //相当于用c[i-1,j-1],c[i-1,j],c[i,j-1]
32     else if(x[i-1]==y[j-1])                         //求解c[i,j]
33         return LCS(x,y,i-1,j-1)+1;
34     else
35         return max(LCS(x,y,i-1,j),LCS(x,y,i,j-1));
36 }

求解最优值写了两个版本,LCS_length 会生成二维数组用来求最优解,LCS用递归直接求值。

下为"abcbdab","bdcaba"的c[i][j]

分析出四种情况:(从下到上)

1、当c[i-1][j]=c[i-1][j-1]=c[i][j-1]=c[i][j]-1时,c[i][j]跳到c[i-1][j-1],并把x[i]加入子串的前面;

 2、3、4、其他情况,c[i][j]跳到和其相等的上位或左位,若有上和左相等,则递归两次。

从右下角开始,由此得到下图

完整代码如下

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <cstdio>
 5 using namespace std;
 6
 7 static int cont = 0;//定义静态变量记共有几种可能
 8 void join(int c[100][100],int i,int j,string s,string x){   //可求出所有可能
 9     if(i==0||j==0){             //到边上停止
10         cout<<s<<endl;          //输出字符串
11         ++cont;                 //计数加一
12         return;
13     }
14     if(c[i-1][j]==c[i-1][j-1]&&c[i][j-1]==c[i-1][j-1]&&c[i][j]-1==c[i-1][j-1]){
15         s =x[i-1]+s;            //c[i-1][j]=c[i-1][j-1]=c[i][j-1]=c[i][j]-1 时子序列加一
16         join(c,i-1,j-1,s,x);
17     }
18     else if(c[i][j] == c[i-1][j]&&c[i][j]!=c[i][j-1])
19         join(c,i-1,j,s,x);      //和上边数相等,向上递归
20     else if(c[i][j] != c[i-1][j]&&c[i][j] == c[i][j-1])
21         join(c,i,j-1,s,x);      //和左边数相等,向左递归
22     else{
23         join(c,i,j-1,s,x);      //两种可能,向上向左
24         join(c,i-1,j,s,x);
25     }
26 }
27 void LCS_length(const string x,const string y){
28     const int m = x.size();
29     const int n = y.size();
30
31     int c[100][100];                //生成二维数组c
32     for(int i = 0;i <= m; ++i)      //i =0 和j = 0时c = 0
33         c[i][0] = 0;
34     for(int j = 1;j <= n; ++j)
35         c[0][j] = 0;
36     for(int i = 1;i <= m; ++i){         //x[i] = y[i]时 c[i][j] =c[i-1][j-1]+1
37         for(int j = 1; j<=n; ++j){      //x[i]不等于y[j]时c[i][j] = max(c[i,j-1],c[i-1,j]
38             if(x[i-1] == y[j-1]){
39                 c[i][j] = c[i-1][j-1]+1;
40             }
41             else if(c[i-1][j] >= c[i][j-1]){
42                 c[i][j] = c[i-1][j];
43             }
44             else{
45                 c[i][j] = c[i][j-1];
46             }
47         }
48     }
49     string b = "";
50     join(c,m,n,b,x);    //递归求出所有最长公共子序列
51 }
52 int LCS(const string x,const string y,int i,int j){ //递归求最优值
53     if(i==0||j==0)
54         return 0;                                   //相当于用c[i-1,j-1],c[i-1,j],c[i,j-1]
55     else if(x[i-1]==y[j-1])                         //求解c[i,j]
56         return LCS(x,y,i-1,j-1)+1;
57     else
58         return max(LCS(x,y,i-1,j),LCS(x,y,i,j-1));
59 }
60
61
62 int main()
63 {
64
65     string a = "abcbdab";
66     string b = "bdcaba";
67     const int m = a.size();
68     const int n = b.size();
69     cout<<"最长公共子序列长度为:"<<LCS(a,b,m,n)<<endl;//输出最优值
70     LCS_length(a,b);
71     cout<<"共"<<cont<<"种可能"<<endl;
72     getchar();
73     return 0;
74 }

结果:

bdab
bcab
bcba
共3种可能

与上图相同。

  

转载于:https://www.cnblogs.com/dk666/p/5947398.html

算法之最长公共子序列(LCS)问题相关推荐

  1. 动态规划算法解最长公共子序列LCS问题

    动态规划算法解LCS问题 作者 July 二零一零年十二月三十一日 本文参考:微软面试100题系列V0.1版第19.56题.算法导论.维基百科. 第一部分.什么是动态规划算法 ok,咱们先来了解下什么 ...

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

    目录 一.概念梳理 二.最长公共子序列解决方案 方案1:蛮力搜索策略 方案2:动态规划策略 三.C代码实现 实现1 实现2(空间优化) 一.概念梳理   1. 子序列(subsequence): 一个 ...

  3. LCS算法:最长公共子序列

    LCS算法:最长公共子序列定义: 一个序列A任意删除若干个字符得到新序列B,则A叫做B的子序列 两个序列X和Y的公共子序列中,长度最长的那个,定义为X和Y的最长公共子序列 例如: X={A,B,C,B ...

  4. 算法学习 - 最长公共子序列(LCS)C++实现

    最长公共子序列 最长公共子序列的问题很简单,就是在两个字符串中找到最长的子序列,这里明确两个含义: 子串:表示连续的一串字符 . 子序列:表示不连续的一串字符. 所以这里要查找的是不连续的最长子序列, ...

  5. java lcs_Java算法之最长公共子序列问题(LCS)实例分析

    本文实例讲述了Java算法之最长公共子序列问题(LCS).分享给大家供大家参考,具体如下: 问题描述:一个给定序列的子序列是在该序列中删去若干元素后得到的序列.确切地说,若给定序列X= { x1, x ...

  6. LCS算法(最长公共子序列问题)

    1.问题 注意:LCS问题即公共子序列问题,不要求所求得的字符在所给的字符串中是连续的(例如:输入两个字符串ABCD和BAC,字符串AC和BC都是它们的最长公共子序列,长度为2.输入两个字符串ABCB ...

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

    最长公共子序列(LCS,Longest Common Subsequence).其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最 ...

  8. 程序员编程艺术第十一章:最长公共子序列(LCS)问题

    程序员编程艺术第十一章:最长公共子序列(LCS)问题 0.前言 程序员编程艺术系列重新开始创作了(前十章,请参考程序员编程艺术第一~十章集锦与总结).回顾之前的前十章,有些代码是值得商榷的,因当时的代 ...

  9. python实现求解最长公共子序列LCS问题

    在实现论文<Automatically Generating Models for Botnet Detection>论文的算法中,用到了一个The longest commom subs ...

最新文章

  1. 澳大利亚悉尼大学徐畅教授招收深度学习方向全奖博士生
  2. IDEA中使用maven下载速度很慢时的解决办法
  3. php 浮点型float 强转int php金额计算 php元转分
  4. linux怎样测试tty,linux – 提示自定义:如何检测何时没有tty
  5. OpenKG 祝大家元宵节快乐!
  6. c语言字面量的作用是为变量,C语言(五) C 全局变量,局部变量,静态变量和常量...
  7. 一分钟读懂java的super关键字
  8. 排球计分程序重构(四)
  9. 即使在移动AI时代,软件仍将主导业界
  10. 一周二次课(12月12日)
  11. java后台restTemplate生成二小程序维码,前端渲染
  12. 打印机质量测试软件,打印机断针测试软件
  13. 本地启动本地mysql_通过本地化启动并运行
  14. 数显之家快讯:【SHIO世硕心语】便携显示器可能是智能手机变身电脑最好的配件了!
  15. mysql创建表时出现10064错误
  16. MacBook 连接投影仪
  17. 十几年稳坐“大哥”位,搞Java的程序员就是这么“牛x”!
  18. 7-15 航空公司VIP客户查询 (25分)(没用stl,哈希链地址法实现)
  19. 网络计算机要不要用电脑,电脑连不上网怎么办 电脑连不上网解决方法
  20. VMware Workstation虚拟机装Win7详细高清图文教程

热门文章

  1. 网络通信 netstat
  2. 解析ip分组_快来看看!!!你经常忽略的TCP/IP知识点~
  3. 2018-11-06 VSAN常见故障
  4. Tomcat学习总结(18)—— Tomcat启动时org.apache.catalina.util.SessionIdGenerator产生安全随机类SecureRandom的实例慢问题解决
  5. Java基础学习总结(87)——坚持写Java等技术类博客的好处
  6. mysql和sqlserver读写分离_C#简单构架之EF进行读写分离+多数据库Mysql/SqlServer
  7. java分页插件使用_MyBatis-Plus之分页插件使用
  8. 三星active2怎么连接手机_手机怎么连接隐藏的wifi无线网络
  9. java爬虫出发onclick事件_java爬虫(五)利用selenium 模拟点击获取动态页面的内容...
  10. 反射 数据类型_这是一份超全面详细的Java反射机制学习指南