最长公共子序列和最长公共子串

也是DP中的经典问题,最长公共子序列(LCS)不用连续而最长公共子串要连续。

最长公共子序列
一个字符串的子序列,是指从该字符串中去掉任意多个字符后剩下的字符在不改变顺序的情况下组成的新字符串。
最长公共子序列,是指多个字符串可具有的长度最大的公共的子序列。
可以用两种算法:递归;DP

算法一:递归
这里写图片描述
代码为:

#include<stdio.h>
#include<string.h>
char a[30], b[30];
int m, n;

int lcs(int i, int j)
{
    if (i >= m || j >= n)   //限定跳出条件
        return 0;
    if (a[i] == b[j])   //相等时都往后移动一位
        return lcs(i+1, j+1) + 1;
    else     //比较得到较大的那一个
        return lcs(i + 1, j) > lcs(i, j + 1) ? lcs(i + 1, j) : lcs(i, j + 1);
}

int main()
{
    strcpy(a, "CNBLOG");
    strcpy(b, "BELONG");//利用strcpy函数存储字符串
    m = strlen(a);
    n = strlen(b);
    printf("%d\n", lcs(0, 0));
    return 0;
}

递归算法容易理解:就是利用一层层往上找,用栈存储,但可以看出,复杂度以指数增长,数据一多就会爆炸,所以一般不用。

算法二:DP
动态规划采用二维数组来标识中间计算结果,避免重复的计算来提高效率。
设有字符串a[0…n],b[0…m],下面就是递推公式。字符串a对应的是二维数组num的行,字符串b对应的是二维数组num的列。
这里写图片描述
另外,采用二维数组flag来记录下标i和j的走向。数字”1”表示,斜向下;数字”2”表示,水平向右;数字”3”表示,竖直向下。这样便于以后的求解最长公共子序列。
代码为:

#include<stdio.h>
#include<string.h>
char a[500], b[500];
int num[500][500];  //作为dp中当前值的存储数组
int flag[500][500];  //标示下标的走向路径,最后根据路径输出最大序列

void lcs()
{
    int i, j;
    for (i = 1; i <= strlen(a); i++) //这里从1开始,但实际上还是从a[0],b[0]开始比较
    {
        for (j = 1; j <= strlen(b); j++)
        {
            if (a[i - 1] == b[j - 1]) //注意这里是i-1和j-1在比较
            {
                num[i][j] = num[i - 1][j - 1]+1;
                flag[i][j] = 1;  //标记为斜向下
            }
            else if (num[i][j - 1] > num[i - 1][j])  //前面一个值大于上面的一个值
            {
                num[i][j] = num[i][j - 1];
                flag[i][j] = 2;  //标记为向右
            }
            else  //前面一个值小于上面的一个值
            {
                num[i][j] = num[i - 1][j];
                flag[i][j] = 3; //标记为向下
            }
        }
    }
}

void getlcs()
{
    char c[500];
    int i = strlen(a);
    int j = strlen(b); //从表的最右下出开始
    int k=0; //用来保存结果的数组标志位
    while (i > 0 && j > 0)
    {
        if (flag[i][j] == 1)
        {
            c[k] = a[i - 1]; //注意是与前一个相等
            k++;
            i--;  //向斜上方移动
            j--;
        }
        else if (flag[i][j] == 2)
            j--;  //向左移动
        else
            i--; //向上移动
    }
    for (i = k-1; i >=0; i--)  //反序输出
        printf("%c", c[i]);
}

int main()
{
    int i;
    strcpy(a, "ABCBDAB");
    strcpy(b, "BDCABA");
    memset(num, 0, sizeof(num));
    memset(flag, 0, sizeof(flag));//先都赋值为0避免对后面干扰
    lcs();
    printf("%d\n", num[strlen(a)][strlen(b)]);
    getlcs();
    return 0;
}

图示:
注意这里为了比较时方便将xi,yi都后移了一位,实际上第一个对应的数组下标仍为0

最长公共子串:
需要连续,反而比子序列简单,还是用二维数组建立一个表格,只要找到最大值和所在位置,从后往前输出就行

代码为:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char a[500], b[500];
int c[500][500];   //存储中间值
int len1, len2, i, j, max = 1, last = 0;//max储存最大值,last存储最大值的位置

int lcs(char *a,char *b)
{
    len1 = strlen(a);
    len2 = strlen(b);
    for (i = 0; i < len1; i++)
    {
        for (j = 0; j < len2; j++)
        {
            if (i == 0 || j == 0)
            {
                if (a[i] == b[j])
                    c[i][j] = 1;
                else
                    c[i][j] = 0;
            }
            else
            {
                if (a[i] == b[j]) //斜向下的情况,很简单的
                    c[i][j] = c[i - 1][j - 1] + 1;
                if (c[i][j]>max)
                {
                    max = c[i][j];
                    last = i;
                }
            }
        }
    }
    return max;
}

int main()
{
    strcpy(a, "BDABC");
    strcpy(b, "ABCBDAB");
    memset(c, 0, sizeof(c));
    printf("%d\n", lcs(a, b));
    for (i = last-max+1; i <= last; i++) //这里需要稍微考虑一下
        printf("%c", a[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值