动态规划最长公共子序列求解以及内存优化

来源:互联网 发布:mac终端默认路径 编辑:程序博客网 时间:2024/06/10 09:08

本文乃本人原创,转载请声明出处;


一、什么是最长公共子序列
   
   什么是最长公共子序列呢?举个简单的例子吧,一个数列S,若分别是两个或多个已知序列的子序列,且是所有符合条件序列中最长的,则S称为已知序列的最长公共子序列。
  举例如下,如:有两个随机数列,1 2 3 4 5 6 和 3 4 5 8 9,则它们的最长公共子序列长度为3   3 4 5。

如果序列为   BDCABA 和 ABCBDAB  那么最长公共子序列长度为4   BCAB 或者 BCBA
  一直不明白:最长公共子串和最长公共子序列的区别。
  
   上网查了下,最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,LCS)的区别为:子串是串的一个连续的部分,子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。


二、考虑了动态规划的性质就是讲一个问题分解为若干子问题,每个子问题最优时当前问题也达到最优  , 那么我们求BDCABA 和 ABCBDAB 的最长公共子序列就可分解为求  BDCA 和 ABCBDA的解 然后加上 BA和B 的解,这样说不清楚,那么先说一下最长公共子序列的3个规律 

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。


这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

求解:

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

问题的规律式写成:


recursive formula

求每一段最长公共子序列的过程:

flow


上面这一段是借鉴于博客 http://blog.csdn.net/yysdsyl/article/details/4226630


黑色的意思是在这个地方 s1[i]=s2[j] 所以a[i][j]的值等于a[i-1][j-1]+1; 箭头就是指这个值是根据哪一个值的出来的

白色呢就是这两个位置的值不一样,比如 s1[4]=A, s2[3]=C(上面的s1s2都是从1开始,实际上是从0开始,所以上面的i,j减1才是在数组s1,s2里面对应的值,这里方便对应就用的i,j的值请自己写程序的时候注意对应i-1,j-1) , 两个值不同,所以就要比较其子问题的最长子序列,选择其中最长的作为当前的最长子序列的长度  即BDCA和AB的最长子序列长度1,和 BDC和ABC的长度2 相比较,2>1所以得出a[4][3]=2;

然后对此我们可以写出程序,利用一个二维数组,通过i,j的嵌套循环 对每一个 a[i][j]的位置利用上述的公式计算出其值,然后输出最后一个值  就是最长公共子序列了,

下面贴上代码


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include<stdio.h>  
  2. #include<string.h>  
  3. #define max(a,b) ((a)>(b)?(a):(b))  
  4. int a[1001][1001]={0};              //定义数组 针对字符串长度小于1000的   
  5. int main()  
  6. {  
  7.     int len1,len2,i,j;  
  8.     char s1[1001],s2[1001];  
  9.     while(~scanf("%s %s",s1,s2))  
  10.     {  
  11.         len1=strlen(s1);          
  12.         len2=strlen(s2);      
  13.         for(i=1;i<=len1;i++)     //循环列   
  14.         {  
  15.             for(j=1;j<=len2;j++) //循环行   
  16.             {  
  17.                 if(s1[i-1]==s2[j-1])    //如果这个s1[i-1]=s2[j-1]的字符相等   
  18.                 a[i][j]=a[i-1][j-1]+1;  //这个位置的值就为其左上角的值加1   
  19.                 else  
  20.                 a[i][j]=max(a[i-1][j],a[i][j-1]);//不相等的话就比较上面和左边的值,得到最大值   
  21.                 printf("%d ",a[i][j]);          //动态规划思想,保证子问题最优,即整体最优   
  22.             }  
  23.             printf("\n");  
  24.         }  
  25.         printf("最长公共子序列为: %3d\n",a[len1][len2]);  
  26.     }  
  27. }  

—————————————————————————————————————————————————————————

————————————————————————————————————————————————————————

内存优化的思考 

这就是动态规划的算法,保持子问题最优,最后则整体最优了,

但是,上述最大问题就是内存,因为每一位的数字都不大,小于字符长度,所以可以用short int 代替int 这样内存优化一半,但是这样依然不是办法,如果字符长度为100000;那么你就没办法解决了,


然后我们可以观察,上面的公式


recursive formula


根据理解,你也可以发现,你对a[i][j]的取值只与3个值有关  字符相等时 a[i-1][j-1],不相等时 a[i-1][j]和a[i][j-1]中大的那个,那么,我们实际上只需要两行数组就ok了,一个是当前a[i]行和a[i-1]行,就可以确定当前值了,并且因为下面的行也只与上一行的值有关,最上面的几行就完全没用了,这样,那我们就可以利用滚动数组的效果,a[2][1001],

比如a[0][1001]的值为第2行的值0 1 1 1 1 2 2 那么我们a[1][1001]就当成当前行,

就可以利用a[0]行计算出a[1],0 1 1 2 2 2 2 ,然后 a[1]代表第3行,那么我们要进行第四行怎么运算呢,


第四行只与第三行有关,也就是当前的a[1],所以把a[0]当成第四行,然后通过a[1]的数据,计算出a[0](第四行) 0 1 1 2 2 3 3 ;就这样交替进行就完全可以把a[1001][1001]变成a[2][1001],那么要怎么实现数组的滚动呢

第一行是a[1],第二行是a[0]。第三行是a[1],第四行是a[0],那么只要一个a[i%2]就可以实现滚动了,那么a[i-1]就是a[(i+1)%2],这样实现了替换,代码就很简单了,只要把所有的a[i]换成a[i%2]就好了,

代码如下

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include<stdio.h>  
  2. #include<string.h>  
  3. #define max(a,b) ((a)>(b)?(a):(b))  
  4. int main()  
  5. {  
  6.     register int i,j;               //常用变量可以这样,提高运行速度   
  7.     int len1,len2,a[2][1001];       //数组减小了,这样可以把字符串大小扩大到几十万   
  8.     char s1[1001],s2[1001];         //几十万的字符的话就必须用int 了 因为short int 范围为 65535   
  9.     while(~scanf("%s %s",s1,s2))  
  10.     {  
  11.         len1=strlen(s1);  
  12.         len2=strlen(s2);  
  13.         memset(a,0,sizeof(a));  //就两行,因为第一行必须初始化为0,所以加一个置0函数   
  14.         for(i=1;i<=len1;i++)  
  15.         {  
  16.             for(j=1;j<=len2;j++)  
  17.             {  
  18.                 if(s1[i-1]==s2[j-1])        //比较字符相等   
  19.                 a[i%2][j]=a[(i+1)%2][j-1]+1;//进行当前行的计算,下一次的时候就自动滚动了   
  20.                 else  
  21.                 a[i%2][j]=max(a[i%2][j-1],a[(i+1)%2][j]);   //依然是自动计算   
  22.                 printf("%d ",a[i%2][j]);   
  23.             }  
  24.             printf("\n");  
  25.         }  
  26.         printf("最长公共子序列长度为:%d\n",a[len1%2][len2]);  
  27.     }  
  28. }  
动态规划最长公共子序列求解及内存优化

这样内存就被优化了,这个是我自己研究的,如果有更好地办法请回复通知我,因为很多人的速度都可以比我快,内存不知道能不能只用4个数表达,但是运行速度可以继续提升,还有优化办法;

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 豆腐废水有臭味怎么办 鸡蛋羹不凝固怎么办 蒸鸡蛋用凉水怎么办 婴儿喝了生水怎么办 小孩碰到脏东西怎么办 出月子没奶怎么办 宝宝钙吸收不好怎么办 宝宝吸收不好长不胖怎么办 一岁半宝宝吸收不好怎么办 婴儿看见脏东西怎么办 母乳变质吃了怎么办 六个月婴儿积食怎么办 儿童吸收功能差怎么办 小孩吸收不好长不胖怎么办 宝贝喝水都吐怎么办 小孩子便秘大便硬结怎么办 一岁半宝宝不爱吃饭怎么办 小孩生病不吃饭怎么办 宝宝吃药不肯咽怎么办 宝宝自己不吃饭怎么办 宝宝突然不想吃饭怎么办 孩子死活不吃药怎么办 小孩死活不吃药怎么办 婴儿不吃药怎么办啊 1周岁宝宝厌食怎么办 宝宝吃药会吐怎么办 宝宝上火拉不出大便怎么办 40天宝宝上火怎么办 宝宝8个月结火了怎么办 婴儿喝牛奶上火怎么办 儿童喝牛奶上火怎么办 三岁宝宝不喝奶怎么办 孩子喝牛奶过敏怎么办 小孩哭晕过去怎么办 宝宝断不了奶怎么办 小孩喝牛奶过敏怎么办 宝宝不爱喝牛奶怎么办? 孕妇喝牛奶恶心怎么办 孕妇喝奶粉上火怎么办 宝宝断奶一直哭怎么办 孕妇喝不了牛奶怎么办