数据结构之字符串的最长公共子序列问题详解与示例(C,C++)

news2025/1/11 11:04:11

文章目录

  • 1、最长公共子序列定义
  • 2、动态规划解法
  • 3、状态转移方程
    • 初始化
    • 构建最长公共子序列
  • 4、C 和 C++ 实现示例
    • C 语言实现
    • C++ 语言实现
  • 5、总结

在这里插入图片描述


字符串的最长公共子序列(Longest Common Subsequence, LCS)是计算机科学中的一个经典问题,属于动态规划(Dynamic Programming, DP)的范畴。在本博客中,我们将详细讲解最长公共子序列的概念,并给出 C 和 C++ 语言的实现示例。

1、最长公共子序列定义

最长公共子序列问题可以这样描述:给定两个字符串序列 X 和 Y,求出它们的最长公共子序列 Z。这里的子序列指的是原序列中元素顺序的连续序列,但不要求元素在原序列中连续。例如,ABCD 和 ACDF 的一个最长公共子序列是 ACD。

2、动态规划解法

动态规划是解决此类问题的一种高效方法,其基本思想是将大问题分解为小问题,先求解小问题,然后利用这些小问题的解来构造原问题的解。对于最长公共子序列问题,我们可以用一个二维数组 dp 来存储两个字符串的前缀的公共子序列长度。

3、状态转移方程

假设我们有两个字符串 X[1…n] 和 Y[1…m],动态规划表 dp[][] 的第 i 行第 j 列的元素表示 X[1…i] 和 Y[1…j] 的公共子序列的长度。状态转移方程如下:

  • 当 X[i] = Y[j] 时,dp[i][j] = dp[i-1][j-1] + 1;
  • 当 X[i] != Y[j] 时,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。

这里 dp[i-1][j-1] 表示 X[1…i-1] 和 Y[1…j-1] 的公共子序列长度,dp[i-1][j] 表示 X[1…i] 和 Y[1…j-1] 的公共子序列长度,dp[i][j-1] 表示 X[1…i-1] 和 Y[1…j] 的公共子序列长度。

初始化

初始化 dp[0][j] = 0 (对于所有 0 <= j < m)和 dp[i][0] = 0 (对于所有 0 <= i < n),因为任何序列与一个空序列都有一个公共子序列长度为0。

构建最长公共子序列

根据动态规划表,我们可以从 dp[n][m] 开始,逆向追踪得到最长公共子序列 Z[1…n+m]。当我们得到 dp[i][j] 时,有两种情况:

如果 X[i] = Y[j],则 Z[k] = X[i] 并且 k++,然后我们递归地求 dp[i-1][j-1];
如果 X[i] != Y[j],则我们分别递归地求 dp[i-1][j] 和 dp[i][j-1],取较大的一个。

4、C 和 C++ 实现示例

下面是使用 C 和 C++ 语言实现最长公共子序列的代码示例:

C 语言实现

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

void printLCS(char X[], char Y[], int dp[][100]) {
    int m = strlen(X);
    int n = strlen(Y);
    int i, j;
    for (i = m, j = n; i > 0 && j > 0; i--, j--) {
        if (X[i - 1] == Y[j - 1]) {
            printf("%c", X[i - 1]);
            X++;
            Y++;
        } else if (dp[i - 1][j] > dp[i][j - 1]) {
            i--;
        } else {
            j--;
        }
    }
}

int LCSLength(char X[], char Y[], int m, int n) {
    int dp[100][100];
    int i, j;

    // 初始化动态规划表
    for (i = 0; i <= m; i++) {
        dp[i][0] = 0;
    }
    for (j = 0; j <= n; j++) {
        dp[0][j] = 0;
    }

    // 动态规划填表
    for (i = 1; i <= m; i++) {
        for (j = 1; j <= n; j++) {
            if (X[i - 1] == Y[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = (dp[i - 1][j] > dp[i][j - 1]) ? dp[i - 1][j] : dp[i][j - 1];
            }
        }
    }

    // 打印最长公共子序列
    printLCS(X, Y, dp);

    return dp[m][n];
}

int main() {
    char X[] = "AGGTAB";
    char Y[] = "GXTXAYB";
    int m = strlen(X);
    int n = strlen(Y);
    printf("最长公共子序列的长度为 %d\n", LCSLength(X, Y, m, n));
    return 0;
}

C++ 语言实现

#include <iostream>
#include <vector>
#include <string>

std::string LCS(const std::string& X, const std::string& Y) {
    int m = X.length();
    int n = Y.length();
    std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));
    std::string lcs;

    // 动态规划填表
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (X[i - 1] == Y[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = (dp[i - 1][j] > dp[i][j - 1]) ? dp[i - 1][j] : dp[i][j - 1];
            }
        }
    }

    // 构建最长公共子序列
    int i = m, j = n;
    while (i > 0 && j > 0) {
        if (X[i - 1] == Y[j - 1]) {
            lcs += X[i - 1];
            i--;
            j--;
        } else if (dp[i - 1][j] > dp[i][j - 1]) {
            i--;
        } else {
            j--;
        }
    }

    // 输出结果
    std::reverse(lcs.begin(), lcs.end());
    std::cout << "最长公共子序列是: " << lcs << std::endl;
    return lcs;
}

int main() {
    std::string X = "AGGTAB";
    std::string Y = "GXTXAYB";
    std::string lcs = LCS(X, Y);
    return 0;
}

在这两个示例中,我们首先初始化了一个动态规划表 dp,然后使用状态转移方程填充它。最后,我们通过回溯动态规划表来构建并打印最长公共子序列。在 C++ 示例中,我们使用了 std::vector 来存储动态规划表,这使得代码更加清晰和易于管理。

5、总结

本文详细介绍了最长公共子序列(LCS)问题的原理,并通过C/C++语言给出了具体的实现。LCS问题是一个经典的动态规划问题,通过构建状态转移方程,我们可以高效地求解两个字符串的最长公共子序列。在实际应用中,LCS问题可以扩展到多个字符串的情况,也可以结合其他算法优化求解过程,如后缀数组、后缀树等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1941633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

08、Tomcat 部署及优化

8.1 Tomcat概述 8.1.1 Tomcat介绍 自从 JSP 发布之后,推出了各式各样的 JSP 引擎。Apache Group 在完成 GNUJSP1.0的开发以后&#xff0c;开始考虑在 SUN 的 JSWDK 基础上开发一个可以直接提供 Web 服务的 JSP服务器&#xff0c;当然同时也支持 Servlet, 这样 Tomcat 就诞生…

函数(递归)

递归&#xff1a;程序调用自身编程技巧称为递归。 在学习递归前需要粗略的了解一下内存&#xff0c;内存分为三类&#xff0c;分别是栈区、堆区和静态区。对于栈区来说&#xff0c;每调用一次函数都会为本次函数开辟一块空间&#xff0c;然而栈区也是有空间限制的&#xff0c;随…

GCN知识总结

关键点&#xff1a; 1.理解图结构的形式 2.如何使用邻接矩阵实现其图结构形式 3.GCN卷积是如何实现节点特征更新的 核心公式&#xff1a; 特征提取&#xff1a; 处理好的x 代表节点特征&#xff0c;然后*权重&#xff0c;再*邻接。 A尖换元后&#xff1a; forward函数 传播规则…

基于SpringBoot+Vue的篮球竞赛预约平台(带1w+文档)

基于SpringBootVue的篮球竞赛预约平台(带1w文档) 基于SpringBootVue的篮球竞赛预约平台(带1w文档) 本系统提供给管理员对首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;项目分类管理&#xff0c;竞赛项目管理&#xff0c;赛事预约管理&#xff0c;系统管理等诸多功…

C++中的模板函数和模板类

模板是 C 中的泛型编程的基础&#xff0c;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。 作为强类型语言&#xff0c;C 要求所有变量都具有特定类型&#xff0c;由程序员显式声明或编译器推导。 但是&#xff0c;许多数据结构和算法无论在哪种类型上操作&#x…

【Java开发实训】day05——数组常见算法

目录 一、数组翻转 1.1示例代码 1.2适用场景 二、冒泡排序 2.1示例代码 2.2适用场景 三、二分查找 3.1示例代码 3.2适用场景 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo…

Elasticsearch中任务管理怎么做?

一个ES集群研发在清理数据时使用了delete_by_query&#xff0c;导致集群性能严重受影响&#xff0c;影响了正常的业务。为了快速恢复集群正常状态&#xff0c;考虑把还在进行的delete_by_query任务清除掉。 先查看当前集群的任务 GET _cat/tasks?v发现delete_by_query的任务…

Git笔记:(上传Git、Git获取文件版本、删除、统计)

目录 一、上传文件到github 1.1 配置用户名和邮箱 1.2 初始化本地仓库 1.3 添加项目目录下所有文件至本地仓库 1.4 使用如下命令加入注释提交 1.5 将本地仓库与远程仓库相连接 1.6 将本地仓库中的文件推送至指定的远程仓库中 二、git获取不同版本 2.1 git下载特定历史…

[MySQL]02 存储引擎与索引,锁机制,SQL优化

Mysql存储引擎 可插拔式存储引擎 索引是在存储引擎底层上实现的 inno DB MySQL默认存储引擎: inno DB高可靠性和高性能的存储引擎 DML操作遵循ACID模型支持事务行级锁,提高并发访问性能支持外键 约束,保证数据完整性和可靠性 MySAM MySAM是MySQL的早期引擎 特点: 不支持事…

【数据结构_C语言】归并排序—文件类型

文章目录 1.排序定义2. 代码实现 1.排序定义 内排序&#xff1a;数据量相对少一些&#xff0c;可以放到内存中排序。 外排序&#xff1a;数据量较大&#xff0c;内存中放不下&#xff0c;数据放到磁盘文件中&#xff0c;需要排序。 归并排序&#xff1a; 2. 代码实现 void…

基于CNN-BiLSTM的数据回归预测

代码原理 基于CNN-BiLSTM的数据回归预测是一种结合卷积神经网络&#xff08;CNN&#xff09;和双向长短期记忆网络&#xff08;BiLSTM&#xff09;的混合模型&#xff0c;用于处理和预测时间序列数据。以下是该方法的简单原理及流程&#xff1a; 原理 &#xff08;1&#xf…

OSGB整体或自定义区域转换为3DTiles

工具 OSGB源数据灵易智模倾斜摄影编辑平台(下称OPEditor) 操作步骤 打开倾斜摄影 打开软件&#xff08;登录即注册&#xff0c;直接用手机号登录&#xff09;使用OPEditor打开OSGB原始倾斜数据&#xff0c;将会看到如下页面 整体导出为3DTiles 在右侧节点树上鼠标右键点…

“为你写诗”,与ai“推敲”码字:第一次在严谨的csdn发布只有“思绪”没有代码的笔记(ai对话实录)

与ai“推敲”码字&#xff0c;第一次在严谨的csdn发布只有“思绪”没有代码的笔记(ai对话实录)。 (笔记模板由python脚本于2024年07月22日 21:13:08创建&#xff0c;本篇笔记适合本文无关代码&#xff0c;爱思考穷探究的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&…

力扣21-30题(数学的简单的结束和数组的前几道)

20240721 1.题和解析693. 交替位二进制数405. 数字转换为十六进制数171 excel 表序列号 从这之后的是数组了2011. 执行操作后的变量值1929. 数组串联1720. 解码异或后的数组异或解析&#xff1a;2574. 左右元素和的差值101. 对称二叉树LCP 06. 拿硬币1365. 有多少小于当前数字的…

org.springframework.context.ApplicationContext发送消息

1、创建消息的实体类 package com.demo;/*** 监听的实体类**/ public class EventMessage {private String name;public EventMessage(String name) {this.name name;}public String getName() {return name;}public void setName(String name) {this.name name;} }2、创建消…

three.js NDC空间转视图空间(getViewPosition)

three.js NDC空间转视图空间 NDC空间转视图空间, 比较常用。很多效果依赖于此。 包括: GTAOShaderReflectorForSSRPassSSRShader 下面看一下它的庐山真面目。 1、计算clipW 视图空间(view) 应用投影矩阵后转换到 剪裁空间(clip)。 [ A 0 B 0 0 C D 0 0 0 E F 0 0 − 1 0 ]…

Truenas scale 配置 Official Zerotier 并设置一直固定Zerotier IP

Zerotier 安装 TrueNAS Scale -> Apps -> Discover Apps -> 搜索 Zerotier 并安装Netwoek 中填入你 zerotier 网页中的的 Network IDAuto Token (API key) zerotier网页的 Account -> API Access Tokens -> New Tokenidentity.secret 和 identity.public 通过 …

国际化技术参考

一、概述 国际化就是用户可以选择对应的语言,页面展示成对应的语言; 一个系统的国际化按照信息的所在位置,可以分为三种国际化信息: 前端页面信息后端提示信息数据库的字典类信息二、前端页面国际化 使用i18n库实现国际化 i18n国际化库思路:通过jquery或者dom操作拿到需…

【动态规划】打家劫舍

动态规划的四个解题步骤&#xff1a; 1、定义子问题 2、写出子问题的递推关系 3、确定dp数组的计算顺序 4、空间优化&#xff08;可选&#xff09;上一篇博客学的有点杂乱&#xff0c;这一篇从解题步骤方面整理一下动态规划这一章。 打家劫舍&#xff08;难度&#xff1a;中等…

C++进阶中继承的全部主要内容

小编在学习完C中继承的内容之后&#xff0c;觉得继承的内容很重要&#xff0c;所以今天给大家带来的内容就是继承的主要内容&#xff0c;今天的内容包括继承的定义与语法&#xff0c;继承方式与权限&#xff0c;基类和派生类的类型转换赋值&#xff0c;继承中的隐藏&#xff0c…