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

---------------------------动态规划的介绍-----------------
    什么是动态规划:动态规划是一种通过把原问题分解成简单的子问题来求解复杂的问题的方法。


    动态规划的适用范围:适用于具有重叠子问题和最有子结构性质的问题。一下是关于使用范围的具体解释。
    重叠子问题:动态规划只解决子问题一次,并且将解决的子问题进行存储,当下次需要这个子问题时直接进行查表获得子问题的解,获得较高的效率。
    最优子结构:特征:原问题的最优解 包含的 子问题的解 也是最优的。
    
    动态规划采用的方法:为了节约求相同子问题的时间,将所有求得的子问题的解用一个数组进行存储。
----------------------问题-----------------------
    求两个字符序列的最长公共子序列。
    什么是字符序列的子序列?
  它的子序列是从给定的字符序列中随意的去掉一些字符后形成的序列。弄清子序列之后就明白了什么是最长的公共子序列了。
---------------------------------思路-----------------
    下面考虑如何用动态规划的方法来求,也就是如何将原问题分解成子问题。
    由于子问题一定比原问题小,所以我们可以从原问题中取出一个字符单独进行分析,这样子问题就少了一个字符,最后将对一个字符的分析结果和子问题结合起来之后就是原问题的解了。OK,现在进行实例的分析。


    假设有字符串x0 x1 x2 x3...xn-1, y0 y1 y2  y3...ym-1。他们的公共子串为z0 z1...zk-1.
如果xn-1 == ym-1,则必有zk-1==xn-1 == ym-1子问题就是考虑x0 x1 x2 x3...xn-2, y0 y1 y2  y3...ym-2的公共子串。
如果xn-1 != ym-1,解决方案如下:
(1)如果zk-1 != ym-1 考虑 x0 x1 x2 x3...xn-1, y0 y1 y2  y3...ym-2的公共子串 
或者(2)如果zk-1 != xn-1 考虑x0 x1 x2 x3...xn-2, y0 y1 y2  y3...ym-1的公共子串
原问题的解应该为二者中的最大长度的子串。
------------------解法-----------------
    ----数组-----动态规划中必须要引入一个数组用来存储子问题的解。子问题为两个字符串的公共子串的长度,由于考虑问题时是从两个字符串的最后一个字符开始考虑的所以可以用最后一个字符的下标i,j来表示,引入一个二维数组c[i][j]来记录x0 x1 x2 x3...xi  和 y0 y1 y2  y3...yj 的最长公共子序列的长度。---动态规划中用数组怎么来记录子问题的解呢?还是比较有意思的。
   -----写公式---算是很重要的一步了,理清题目的思路之后就是写公式了。是自顶向下的。
c[i][j] = 0  (if i==0 or j==0)
c[i][j] = c[i-1][j-1]+1  (if i j>0 and xi == yj)
c[i][j] = max(c[i-1][j],c[i][j-1])  (if i j>0 and xi != yj)
写公式时候后面的条件很重要,一定要写明白。
---------------代码-------------
    好了,公式写明白了,接下来就是具体的代码了。
#include <stdio.h>
#include <string.h>
#define MAXLEN 100
int c[][MAXLEN];
int b[MAXLEN];//表示方向的数组 为0表示向左上方,为1表示向上 -1表示向下
void LCSLength(char *str1,char*str2){
if(!str1||!str2)
return 0;
int len1 = strlen(str1);
int len2 = strlen(str2);
if (!len1||!len2)
return 0;
int i,j;
for (i=0;i<len1;i++)
c[i][0]=0;
for (j=0;j<len2;j++)
c[0][j]=0;




for (i=1;i<=len1;i++)
{
for (j=1;j<=len2;j++)
{
if (str1[i-1]==str2[j-1])
{
c[i][j] = c[i-1][j-1]+1;
b[i][j] = 0;
}
else
if (c[i-1][j]>=c[i][j-1])
{
c[i][j] = c[i-1][j];
b[i][j] = 1;
}
else
{
c[i][j] = c[i][j-1];
b[i][j] = -1;
}


}
}
}
必须注意一点,为了使一开始就能使用公式求c[][MAXLEN]的值,必须将第一行全设置为0,第一列全设置为0.而且c[i][j]表示x字符串从第一到第i个字符,y字符串从第一到第j个字符时的公共子序列最长的长度。
    剩下的问题是如何根据所求的字符数组c[][MAXLEN]输出最长公共子序列。可以设置一个数组b[][MAXLEN],用来记录字符串是如何相等的。
   比如x[i-1]==y[j-1],有c[i][j]=c[i-1][j-1]+1,设置b[i][j]=0.判断当b[i][j]==0时候可以输出字符。
   当c[i][j] = c[i-1][j],可以设置b[i][j]=1,当b[i][j]=1时候,应该将x子串缩小一个字符,再输出LCS。    当c[i][j] = c[i][j-1];可以设置b[i][j]=-1,当b[i][j]=-1,应该将y子串缩小一个字符然后输出LCS。
   依据b[][MAXLEN]数组,输出函数也用递归进行输出。
void print_LCS(int b[][MAXLEN],char *x,int i,int j){
if (i==0||j==0)
{
return;
}
if (b[i][j]==0)
{
printf("%c",x[i-1]);
}
if (b[i][j]=1)
{
print_LCS(b,x,i-1,j);
}
else
print_LCS(b,x,i,j-1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值