最长公共子序列问题的求解

假设有两个字符串A和B,A字符串的组成为 A = A 0 A 1 A 2 . . . . . . A n − 1 A = A_0A_1A_2......A_{n-1} A=A0A1A2......An1
B = B 0 B 1 B 2 . . . . . . B m − 1 B=B_0B_1B_2......B_{m-1} B=B0B1B2......Bm1
要寻找这两个字符串的公共子序列还是最长的那个,这怕是有点难哦!
我首先想到的就是暴力求解,分别用A串中的每一个子串去匹配B串中的子串,只要匹配上了就说明这是一个公共的子串,然后记录下来此子串作为目前碰到的最长子串,继续匹配更长的子串,如果存在更长的公共子串就更新我们记录的最长子串,直到所有可能都已经验证过,这样我们就使用暴力算法求解出了该问题的解。

暴力算法时间分析

由于暴力算法是利用子串来进行匹配求解的,显然要求解A串和B串的每一对子串的配对,那么就要求解 n u m A ∗ n u m B num_A*num_B numAnumB其中 n u m A num_A numA是A中的子串个数, n u m B num_B numB是B中子串个数,要求A中的子串个数可以这样考虑,A中的一个字符要么在A的子串里要么不在A的子串里,第一个字符要么出现在其子串中,要么不出现在其子串中,第二个字符同样的道理一直到第n个字符,由此A的子串有 2 ∗ 2 ∗ 2 ∗ . . . . . . ∗ 2 ∗ 2 ( n 个 2 相乘 ) = 2 n 2*2*2*......*2*2(n个2相乘)=2^n 222......22(n2相乘)=2n
同理B串有 2 m 2^m 2m个子串,因为要求解 2 m + n 2^{m+n} 2m+n由此可以判定暴力算法求解的时间复杂度为O( 2 n 2^n 2n)。
虽然可以在这种算法的基础上进行优化,但是优化过后仍然是指数级的时间复杂度。
这指数级的时间复杂度似乎有点太高了吧!我无法接受。

动态规划算法

最长公共子序列问题具有最优子结构性质。
假设A串与B串的最长公共子序列为 C = C 0 C 1 C 2 . . . . . . C k C=C_0C_1C_2......C_k C=C0C1C2......Ck那么我们可以作以下推理:

  1. 如果 A n − 1 = = B m − 1 A_{n-1} == B_{m-1} An1==Bm1那么 C k = = A n − 1 = = B m − 1 C_k == A_{n-1} == B_{m-1} Ck==An1==Bm1同时 C 余 = C 0 C 1 C 2 . . . . . . C k − 1 C_余 = C_0C_1C_2......C_{k-1} C=C0C1C2......Ck1 A = A 0 A 1 A 2 . . . . . . A n − 2 A = A_0A_1A_2......A_{n-2} A=A0A1A2......An2
    B = B 0 B 1 B 2 . . . . . . B m − 2 B=B_0B_1B_2......B_{m-2} B=B0B1B2......Bm2的最长公共子序列。这很好理解对吧!
  2. 如果 A n − 1 ≠ B m − 1 A_{n-1}\neq B_{m-1} An1=Bm1 C k ≠ A n − 1 C_k \neq A_{n-1} Ck=An1那么 C = C 0 C 1 C 2 . . . . . . C k C=C_0C_1C_2......C_k C=C0C1C2......Ck A = A 0 A 1 A 2 . . . . . . A n − 2 A = A_0A_1A_2......A_{n-2} A=A0A1A2......An2
    B = B 0 B 1 B 2 . . . . . . B m − 1 B=B_0B_1B_2......B_{m-1} B=B0B1B2......Bm1的公共子序列
  3. 如果 A n − 1 ≠ B m − 1 A_{n-1}\neq B_{m-1} An1=Bm1 C k ≠ B m − 1 C_k \neq B_{m-1} Ck=Bm1那么 C = C 0 C 1 C 2 . . . . . . C k C=C_0C_1C_2......C_k C=C0C1C2......Ck A = A 0 A 1 A 2 . . . . . . A n − 1 A = A_0A_1A_2......A_{n-1} A=A0A1A2......An1
    B = B 0 B 1 B 2 . . . . . . B m − 2 B=B_0B_1B_2......B_{m-2} B=B0B1B2......Bm2的公共子序列

这说明当前串的最长公共子序列包含了这两个序列前缀的最长公共子序列,这说明此问题具有最优子结构。

确定递归结构

当其中有一个串的长度为0的时候则递归求解的时候这种情况最长公共子序列的长度就为0,当 A n − 1 = = B m − 1 A_{n-1} == B_{m-1} An1==Bm1时,就是 A = A 0 A 1 A 2 . . . . . . A n − 2 A = A_0A_1A_2......A_{n-2} A=A0A1A2......An2 B = B 0 B 1 B 2 . . . . . . B m − 2 B=B_0B_1B_2......B_{m-2} B=B0B1B2......Bm2这两个最长公子序列长度加1,当 A n − 1 ≠ B m − 1 A_{n-1} \neq B_{m-1} An1=Bm1则子序列的长度为 A = A 0 A 1 A 2 . . . . . . A n − 1 A = A_0A_1A_2......A_{n-1} A=A0A1A2......An1 B = B 0 B 1 B 2 . . . . . . B m − 2 B=B_0B_1B_2......B_{m-2} B=B0B1B2......Bm2的最长公共子序列长度和 A = A 0 A 1 A 2 . . . . . . A n − 2 A = A_0A_1A_2......A_{n-2} A=A0A1A2......An2 B = B 0 B 1 B 2 . . . . . . B m − 1 B=B_0B_1B_2......B_{m-1} B=B0B1B2......Bm1的最长公共子序列长度中的最大值

a n s [ i ] [ j ] = { 0 i = 0 或 j = 0 a n s [ i − 1 ] [ j − 1 ] + 1 A n − 1 = = B m − 1 m a x ( a n s [ i ] [ j − 1 ] , a n s [ i − 1 ] [ j ] ) A n − 1 ≠ B m − 1 ans[i][j] = \begin{cases} 0 &i=0或j=0 \\ ans[i-1][j-1] + 1 &A_{n-1} == B_{m-1} \\ max(ans[i][j-1], ans[i-1][j]) & A_{n-1} \neq B_{m-1} \end{cases} ans[i][j]= 0ans[i1][j1]+1max(ans[i][j1],ans[i1][j])i=0j=0An1==Bm1An1=Bm1
这样我们就能求出整个字符串对应的矩阵列表,其中i表示字符串A的长度,j表示求解B串的长度ans[i][j]表示串A在为前i个字符组成的串的情况下B串为前j个字符组成的串的情况下最长公共子串的长度。
我们求出来的是一个长度矩阵,求解此矩阵只需要O(m*n)的时间复杂度,
然后根据此矩阵我们可以进一步确定整个最长公共子串是什么。这一步只需要O(m+n)时间复杂度,因此整个程序的时间复杂度为O(mn)
同时可以再使用一个矩阵记录一个子问题由哪个子问题得到,这样回溯就只需要根据此矩阵进行回溯即可。
由此可以写出下述代码

int LCLLength(int m, int n, char *x, char *y, int **c, int **b){int i, j;for(int i=1; i<m; i++){c[i][0] = 0; // 其中一个子串长度为0}for(i=1; i<=n; i++){c[0][i] = 0 // 其中一个子串长度为0}for(i=1; i<=m; i++>){for(j=1; j<n; j++){if(x[i] == y[j]){c[i][j] = c[i-1][j-1]+1;b[i][j] = 1;//;记录发生了第一种情况,这样回溯的时候查找A中第i个字符等于B中第j个字符}else if(c[i-1][j] >= c[i][j-1]){c[i][j] = c[i-1][j];b[i][j] = 2;// 说明A中此处的第i个字符不属于公共子串}else{c[i][j] = c[i][j-1];b[i][j] = 3;// 说明此处B串中第j个字符不属于公共字符串}}}
}

在回溯的过程中,可以得到要的字符串。

void LCS(int i, int j, char *x, int **b){if(i==0||j==0){return;}if(b[i][j] == 1){// 说明A中此i处属于公共子序列LCS(i-1,j-1, x,b);printf("%c", x[i]); // 使用B串就使用j下标}else if (b[i][j] == 2){LCS(i-1,j,x,b);}else{LCS(i,j-1, x,b);}
}

这样我们就解决了整个最长公共子序列问题。
完整可运行代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int LCLLength(int m, int n, char *x, char *y, int **c, int **b){int i, j;for(i=1; i<m; i++){c[i][0] = 0; // 其中一个子串长度为0}for(i=1; i<=n; i++){c[0][i] = 0;// 其中一个子串长度为0}for(i=1; i<=m; i++){for(j=1; j<=n; j++){if(x[i-1] == y[j-1]){c[i][j] = c[i-1][j-1]+1;b[i][j] = 1;//;记录发生了第一种情况,这样回溯的时候查找A中第i个字符等于B中第j个字符}else if(c[i-1][j] >= c[i][j-1]){c[i][j] = c[i-1][j];b[i][j] = 2;// 说明A中此处的第i个字符不属于公共子串}else{c[i][j] = c[i][j-1];b[i][j] = 3;// 说明此处B串中第j个字符不属于公共字符串}}}
}
void LCS(int i, int j, char *x, int **b){if(i==0||j==0){return;}if(b[i][j] == 1){// 说明A中此i处属于公共子序列LCS(i-1,j-1, x,b);printf("%c", x[i-1]); // 使用B串就使用j下标}else if (b[i][j] == 2){LCS(i-1,j,x,b);}else{LCS(i,j-1, x,b);}
}
int main(void) {char x[] = "ab";char y[] = "abc";int **a = (int **)malloc(sizeof(int*)*(strlen(x)+1));for(int i=0; i<strlen(x)+1; i++) {a[i] = (int *)malloc(sizeof(int)*(strlen(y)+1));}int **b = (int **)malloc(sizeof(int*)*(strlen(x)+1));for(int i=0; i<strlen(x)+1; i++) {b[i] = (int *)malloc(sizeof(int)*(strlen(y)+1));}for(int i=0; i<strlen(x)+1; i++) {for(int j=0; j<strlen(y)+1; j++) {printf("%4d", b[i][j]);}printf("\n");}LCLLength(strlen(x), strlen(y), x,y,a,b);printf("\n");for(int i=0; i<strlen(x)+1; i++) {for(int j=0; j<strlen(y)+1; j++) {printf("%4d", b[i][j]);}printf("\n");}LCS(strlen(x), strlen(y), x, b);return 0;
}

其实我们不使用b标记矩阵也能进行回溯,只不过需要进行判断而已。
参考书籍:计算机算法设计与分析第五版(王晓东编著)

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

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

相关文章

MS Excel: 高亮当前行列 - 保持原有格式不被改变

本文使用条件格式VBA的方法实现高亮当前行列&#xff0c;因为纯VBA似乎会清除原有的高亮格式。效果如下&#xff1a;本文图省事就使用同一种颜色了。 首先最重要的&#xff0c;【选中你期望高亮的单元格区域】&#xff0c;比如可以全选当前sheet的全部区域 然后点击【开始】-【…

06.深入学习Java 线程

1 线程的状态/生命周期 Java 的 Thread 类对线程状态进行了枚举&#xff1a; public class Thread implements Runnable {public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;} } 初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用…

数据库学习笔记1-数据库实验1

文章目录 创建表格的时候出现的一些错误查询所有的表格实验一查询单个表格分块修改大学数据库表格创建大学数据库表格系课程教师课程段授课学生选课注意吐槽 修改大学数据库表格2&#xff08;英文版本&#xff09;abcde 自建项目-在线书店数据库 创建表格的时候出现的一些错误 …

子集和问题(回溯法)

目录 ​​​​ 前言 一、算法思路 二、分析过程 三、代码实现 伪代码&#xff1a; C&#xff1a; 总结 前言 【问题描述】考虑定义如下的PARTITION问题中的一个变型。给定一个n个整数的集合X{x1,x2,…,xn}和整数y&#xff0c;找出和等于y的X的子集Y。 一、算法思路 基本思想&am…

【STL】C++ stack(栈) 基本使用

目录 一 stack常见构造 1 空容器构造函数&#xff08;默认构造函数&#xff09; 2. 使用指定容器构造 3 拷贝构造函数 二 其他操作 1 empty 2 size 3 top 4 push && pop 5 emplace 6 swap 三 总结 一 stack常见构造 1 空容器构造函数&#xff08;默认构造…

云计算OpenStack基础

1.什么是虚拟化&#xff1f; •虚拟化是云计算的基础。 •虚拟化是指计算元件在虚拟的而不是真实的硬件基础上运行。 •虚拟化将物理资源转变为具有可管理性的逻辑资源&#xff0c;以消除物理结构之间的隔离&#xff0c;将物理资源融为一个整体。虚拟化是一种简化管理和优化…

探秘AI艺术:揭开Midjourney绘画的神秘面纱

在当今这个数字化迅速发展的时代&#xff0c;AI技术已经深入到我们生活的方方面面&#xff0c;而最令人着迷的莫过于它在艺术创作领域的应用。“Midjourney绘画”就是这样一个令人惊叹的例子&#xff0c;它通过高级AI技术&#xff0c;能够帮助用户生成独一无二的艺术作品。但是…

如何知道自己电脑的 Shell类型是什么?

在macOS中&#xff0c;你可以通过以下几种方法来确定当前正在使用的shell类型&#xff0c;并了解相关的配置文件&#xff1a; 1. 使用终端命令确定shell类型 打开终端应用程序&#xff08;Terminal&#xff09;。输入以下命令并按回车键&#xff1a;echo $SHELL。该命令会输出…

最长递增子序列,交错字符串

第一题&#xff1a; 代码如下&#xff1a; int lengthOfLIS(vector<int>& nums) {//dp[i]表示以第i个元素为结尾的最长子序列的长度int n nums.size();int res 1;vector<int> dp(n, 1);for (int i 1; i < n; i){for (int j 0; j < i; j){if (nums[i]…

[日常开发] 数据库主从延迟问题

MySQL数据库主从延迟问题 无论是学习还是工作中&#xff0c;MySQL数据库的使用都十分地广泛。在业务中&#xff0c;数据库也会以集群的形式使用&#xff0c;所以会涉及到主从问题。 问题描述 在使用MySQL数据库的时候&#xff0c;在service的方法中首先向A数据表批量插入了数…

Spring-注解

Spring 注解分类 Spring 注解驱动模型 Spring 元注解 Documented Retention() Target() // 可以继承相关的属性 Inherited Repeatable()Spirng 模式注解 ComponentScan 原理 ClassPathScanningCandidateComponentProvider#findCandidateComponents public Set<BeanDefin…

动态规划part03 Day43

LC343整数拆分&#xff08;未掌握&#xff09; 未掌握分析&#xff1a;dp数组的含义没有想清楚&#xff0c;dp[i]表示分解i能够达到的最大乘积&#xff0c;i能够如何分解呢&#xff0c;从1开始遍历&#xff0c;直到i-1&#xff1b;每次要不是j和i-j两个数&#xff0c;要不是j和…

【传知代码】自监督高效图像去噪(论文复现)

前言&#xff1a;在数字化时代&#xff0c;图像已成为我们生活、工作和学习的重要组成部分。然而&#xff0c;随着图像获取方式的多样化&#xff0c;图像质量问题也逐渐凸显出来。噪声&#xff0c;作为影响图像质量的关键因素之一&#xff0c;不仅会降低图像的视觉效果&#xf…

串口通信问题排查总结

串口通信问题排查 排查原则&#xff1a; 软件从发送处理到接收处理&#xff0c;核查驱动、控制及发送接收数据是否正常。硬件从发送到接收&#xff0c;针对信号经过的各段&#xff0c;分段核对信号是否正常。示波器、逻辑分析仪。用万用表、示波器、逻辑分析仪等工具&#xf…

JRT性能演示

演示视频 君生我未生&#xff0c;我生君已老&#xff0c;这里是java信创频道JRT&#xff0c;真信创-不糊弄。 基础架构决定上层建筑&#xff0c;和给有些品种的植物种植一样&#xff0c;品种不对&#xff0c;施肥浇水再多&#xff0c;也是不可能长成参天大树的。JRT吸收了各方…

基于文本来推荐相似酒店

基于文本来推荐相似酒店 查看数据集基本信息 import pandas as pd import numpy as np from nltk.corpus import stopwords from sklearn.metrics.pairwise import linear_kernel from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extrac…

C++:类和对象

一、前言 C是面向对象的语言&#xff0c;本文将通过上、中、下三大部分&#xff0c;带你深入了解类与对象。 目录 一、前言 二、部分&#xff1a;上 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 5.类的作用域 6.类的实例化 7.类的…

JavaScript条件语句与逻辑判断:解锁代码逻辑的奥秘【含代码示例】

JavaScript条件语句与逻辑判断&#xff1a;解锁代码逻辑的奥秘【含代码示例】 基本概念与作用if...else&#xff1a;决策的基础switch&#xff1a;多路分支的能手逻辑运算符&#xff1a;连接逻辑的纽带三元运算符&#xff1a;简洁的力量 功能使用思路与技巧短路求值优化防止swi…

FindBI学习总结

大数据分析BI工具&#xff1a;用户只需简单拖拽便能制作出丰富多样的数据可视化信息 关注点&#xff1a; 快速入门、数据加工、构建图表和分析数据、数据分析进阶 1、界面介绍 目录–仪表板–数据准备 仪表板目录–预览区域 快速上手&#xff1a; 1、数据准备2、制作仪表板3、分…

springboot项目使用validated参数校验框架

目录 前言 一、validated是什么&#xff1f; 二、使用步骤 1.引入maven依赖 2.使用实现 总结 前言 当谈到Spring的参数校验功能时&#xff0c;Validated注解无疑是一个重要的利器。它为我们提供了一种简单而又强大的方式来验证请求参数的合法性&#xff0c;保证了系统的稳…