动态规划---求最长公共子序列和最长公共子串(C语言)

参考:https://blog.youkuaiyun.com/someone_and_anyone/article/details/81044153

一、求最长公共子序列

1、问题描述:

从一个给定的串中删去(不一定连续地删去)0个或0个以上的字符,剩下地字符按原来顺序组成的串。例如:“ ”,“a”,“xb”,“aaa”,“bbb”,“xabb”,“xaaabbb”都是串“xaaabbb”的子序列。(例子中的串不包含引号。)

要求:给定两个序列 X = { x 1 , x 2 , … , x m } X=\{x_1,x_2,…,x_m\} X={x1,x2,,xm} Y = { y 1 , y 2 , … , y n } Y=\{y_1,y_2,…,y_n\} Y={y1,y2,,yn},找出X和Y的最长公共子序列。

输入:输入数据有多组,每组有两行 ,每行为一个长度不超过500的字符串(输入全是大写英文字母(A,Z)),表示序列X和Y。

输出:每组输出一行,表示所求得的最长公共子序列的长度,若不存在公共子序列,则输出0。

样例输入:

13456778
357486782

样例输出:

5

最长公共子串是35678

二、解决方法:

1、暴力解法:
假设 m < n m<n m<n, 对于母串 X X X,我们可以暴力找出 2 m 2^m 2m个子序列,然后依次在母串 Y Y Y中匹配,算法的时间复杂度会达到指数级 O ( n ∗ 2 m ) O(n∗2^m) O(n2m)。显然,暴力求解不太适用于此类问题。

2、递归求解:
x m = y n x_m=y_n xm=yn时,找出 X m − 1 X_{m-1} Xm1 Y n − 1 Y_{n-1} Yn1的最长公共子序列,然后在其尾部加上 x m ( = y n ) x_m(=y_n) xm(=yn)即可得X和Y的一个最长公共子序列。当 x m ≠ y n x_m≠y_n xm=yn时,必须解两个子问题,即找出 X m − 1 X_{m-1} Xm1 Y Y Y的一个最长公共子序列及 X X X Y n − 1 Y_{n-1} Yn1的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。
在算法LCS中,每一次的递归调用使i或j减1,因此算法的计算时间为 O ( m + n ) O(m+n) O(m+n)

3、动态规划:
(1)创建DP数组C[ ] [ ]
在插入图片描述
(2)把首行和首列置零
在这里插入图片描述
(3)按照以下规则把表填满
在这里插入图片描述
在这里插入图片描述
C[8][9]=5即为所要求的最长公共子序列的长度

代码如下:

#include<stdio.h>
#include<string.h>

int max(int a,int b)
{
   return a>b?a:b;
}
int main()
{
    int i,j,k;
    char a[600];
    char b[600];
    int f[600][600];
    while(scanf("%s %s",a,b)!=EOF)
    {
        int n=strlen(a);
        int m=strlen(b);
        for(i=0;i<=n;i++)
        {
            f[i][0]=0;
        }
        for(i=0;i<=m;i++)
        {
            f[0][i]=0;
        }
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                if(a[i-1]==b[j-1])
                {
                    f[i][j]=f[i-1][j-1]+1;
                }
                else
                {
                    f[i][j]=max(f[i-1][j],f[i][j-1]);
                }
            }
        }
        printf("%d\n",f[n][m]);
    }
    return 0;
}

S1和S2的最LCS并不是只有1个,本文并不是着重讲输出两个序列的所有LCS,只是介绍如何通过上表,输出其中一个LCS。
我们根据递归公式构建了上表,我们将从最后一个元素c[8][9]倒推出S1和S2的LCS。
c[8][9] = 5,且S1[8] != S2[9],所以倒推回去,c[8][9]的值来源于c[8][8]的值(因为c[8][8] > c[7][9])。
c[8][8] = 5, 且S1[8] = S2[8], 所以倒推回去,c[8][8]的值来源于 c[7][7]。
以此类推,如果遇到S1[i] != S2[j] ,且c[i-1][j] = c[i][j-1] 这种存在分支的情况,这里请都选择一个方向(之后遇到这样的情况,也选择相同的方向)

第一种结果为:

在这里插入图片描述
这就是倒推回去的路径,棕色方格为相等元素,即LCS = {3,4,6,7,8},这是其中一个结果。

如果如果遇到S1[i] != S2[j] ,且c[i-1][j] = c[i][j-1] 这种存在分支的情况,选择另一个方向,会得到另一个结果。
在这里插入图片描述
即LCS ={3,5,7,7,8}。

二、DP求解最长公共子串

前面提到了子串是一种特殊的子序列,因此同样可以用DP来解决。定义数组的存储含义对于后面推导转移方程显得尤为重要,糟糕的数组定义会导致异常繁杂的转移方程。考虑到子串的连续性,将二维数组c[i][j]用来记录具有这样特点的子串——结尾同时也为为串x1x2⋯xi与y1y2⋯yj的结尾——的长度。
得到转移方程:
在这里插入图片描述
最长公共子串的长度为 max(c[i,j]), i∈{1,⋯,m},j∈{1,⋯,n}。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值