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

本文介绍如何使用动态规划解决最长公共子序列问题。通过构建二维数组记录最长公共子序列的长度及回溯路径,最终实现高效求解。文章还提供了详细的算法实现步骤及代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天自学了动态规划算法,下面这是一个最长公共子序列的问题
解释可能不是很详细,大家尽力看吧。
最长公共子序列是指:给定两个序列X={x1,x2,…,xm}和Y={y1,y2,y3,…yn},找出X和Y的一个最长公共子序列。

暴力解的话需要穷举X的所有子序列,检查每个子序列是否也是Y的子序列,记录找到的最长公共子序列。X的子序列有2的m次方个,所以暴力解的时间复杂度为指数阶。我们可以用动态规划来解决这道题。

我们可以假设c[i][j]表示Xi和Yj的最长公共子序列长度。
可以得到
    { 0        i=0或者j=0
c[i][j]= {c[i-1][j-1]+1    i,j>0且xi=yj
    { max{c[i][j-1]+1}  i,j>0且xi≠yj
    
自底向下构造最优值,并记录最优值和最有策略
i=1时:{x1}和{y1,y2,…,yn}中字符一一比较,按递归式求解并记录最长公共子序列长度。
i=2时:{x1}和{y1,y2,…,yn}中字符一一比较,按递归式求解并记录最长公共子序列长度。

i=m时:{x1}和{y1,y2,…,yn}中字符一一比较,按递归式求解并记录最长公共子序列长度。

上面只是求出最长公共序列的长度,并不知道最长公共子序列是什么
如果求出c[m][n]=5,表示Xm和Yn的最长公共子序列时5,那么我们可以反向追溯5是哪里来的。根据递推式
xi=yj时:c[i][j]=c[i-1][j-1]+1;
xi≠yj时:c[i][j]=max{c[i][j-1],c[i-1][j]};
那么c[i][j]来源一共有三个:c[i][j]=c[i-1][j-1]+1,c[i][j]=c[i][j-1],c[i][j]=c[i-1][j]。我们可以用一个辅助数组b[i][j]记录这3个来源:
c[i][j]=c[i-1][j-1]+1, b[i][j]=1;
c[i][j]=c[i][j-1] ,b[i][j]=2;
c[i][j]=c[i-1][j],, b[i][j]=3;
这样就可以根据b[m][n]反向追踪最长公共子序列,当b[i][j]=时,输出xi,当b[i][j]=2时,追踪c[i][j-1];当b[i][j]=3时,追踪c[i-][j],直到i=0或者j=0停止

如果不是很理解,可以试着画个二维数组的图试一遍

(1)最长公共子序列求解函数
首先计算两字符长度,然后i=1开始,s1[0]与s2中的每一个比较
如果字符相同,则公共序列长度为c[i-1][j-1]+1,并记录最优策略来源b[i][j]=1。
如果当前字符不相同,则公共序列长度为c[i][j-1] 和c[i-1][j]中的最大值,如果c[i][j-1]>=c[i-1][j],则最有策略来源b[i][j]=2;如果c[i][j-1]<c[i-1][j],则最优策略来源b[i][j]=3。直到i>len1,算法结束,这时从c[len1][len2]就是我们要的最长公共序列长度。代码如下:

for(i=1;i<=len1;i++)//控制a1序列
                for(j=1;j<=len2;j++)//控制a2序列
            {
                if(a1[i-1]==a2[j-1])//如果当前字符相同,则公共子序列的长度为该
                //字符前的最长公共序列+1;
                {
                    c[i][j]=c[i-1][j-1]+1;
                    b[i][j]=1;
                }
                else
                {
                    if(c[i][j-1]>=c[i-1][j])//找两者最大值,并记录最优策略
                        b[i][j]=2,c[i][j]=c[i][j-1];
                    else
                    {
                        c[i][j]=c[i-1][j];
                        b[i][j]=3;
                    }
                }
            }

(2)最优解函数输出
倒推法:如果b[i][j]=1,说明s1[i-1]=s2[j-1],那么我们就可以递归输出print(i-1,j-1);然后输出s1[i-1]。
如果b[i][j]=2,说明s1[i-1]≠s2[j-1]且最优解来源于c[i][j]=c[i][j-1],递归输出print(i,j-1)。
如果b[i][j]=3,说明s1[i-1]≠s2[j-1]且最优解来源于c[i][j]=c[i-1][j],递归输出print(i,j-1)。
当i0||j0时,递归结束。代码如下

void print(int i,int j)
{
    if(i==0||j==0)//i=0或者j=0说明回溯完成
        return ;
    if(b[i][j]==1)
    {
        print(i-1,j-1);
        printf("%c ",a1[i-1]);
    }
    else if(b[i][j]==2)
        print(i,j-1);
        else print(i-1,j);
}

总代码如下

#include<stdio.h>
#include<string.h>
int b[100][100],c[100][100];
char a1[100],a2[100];//定义两个字符串数组;
void print(int i,int j)
{
    if(i==0||j==0)//i=0或者j=0说明回溯完成
        return ;
    if(b[i][j]==1)
    {
        print(i-1,j-1);
        printf("%c ",a1[i-1]);
    }
    else if(b[i][j]==2)
        print(i,j-1);
        else print(i-1,j);
}
int main()
{
    int len1,len2,i,j;
    while(~scanf("%s%s",a1,a2))
    {
        len1=strlen(a1);
        len2=strlen(a2);
        for(i=1;i<=len1;i++)//控制a1序列
            for(j=1;j<=len2;j++)//控制a2序列
        {
            if(a1[i-1]==a2[j-1])//如果当前字符相同,则公共子序列的长度为该
            //字符前的最长公共序列+1;
            {
                c[i][j]=c[i-1][j-1]+1;
                b[i][j]=1;
            }
            else
            {
                if(c[i][j-1]>=c[i-1][j])//找两者最大值,并记录最优策略
                    b[i][j]=2,c[i][j]=c[i][j-1];
                else
                {
                    c[i][j]=c[i-1][j];
                    b[i][j]=3;
                }
            }
        }
        print(len1,len2); //递归构成最长共共序列最优解
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值