最长公共子串LCS (Longest Common Subsequence) 算法

article/2025/9/11 11:11:36

三个方法都有所借鉴,但代码部分是自己试着写出来的,虽然最后的运行结果都是正确的,但此过程中难免会有考虑不周全的地方,如发现代码某些地方有误,欢迎指正。同时有新的想法,也可以提出!

采用顺序结构存储串,求串s和串t的一个最长公共子串。

方法一

  • 算法思想:借助二维数组可以求出最长公共子串最后一个字符分别在s串和t串中的数组下标。此时可以从s中截取最长公共子串,也可以从t中截取。
SqString MaxComStr(SqString s, SqString t)
{SqString str;int arr[MaxSize][MaxSize];int i, j, k, maxi[2], maxlen = 0;for (i = 0; i < s.length; i++)for (j = 0; j < t.length; j++){if (s.data[i] == t.data[j]){if (i == 0 || j == 0) arr[i][j] = 1;else{arr[i][j] = arr[i - 1][j - 1] + 1;if (arr[i][j] > maxlen){maxlen = arr[i][j];maxi[0] = i;maxi[1] = j;}printf("\ni=%d j=%d arr=%d", i, j, arr[i][j]);}}else arr[i][j] = 0;}else arr[i][j] = 0;}printf("\n二维数组中最大值的行号为:%d 列号为:%d\n", maxi[0], maxi[1]);//输出二维数组for (i = 0; i < s.length; i++){for (j = 0; j < t.length; j++)printf("%2d", arr[i][j]);printf("\n");}//求公共子串  代码二选一即可(行号截取/列号截取)/*for (k = maxlen - 1, i = maxi[0]; k >= 0; k--, i--) //用行号从s串中截取{str.data[k] = s.data[i];str.length = maxlen;}*/for (k = maxlen - 1, i = maxi[1]; k >= 0; k--, i--) //用列号从t串中截取{str.data[k] = t.data[i];str.length = maxlen;}//输出strprintf("\n最长公共子串为:");DispStr(str);printf("\n");return str;
}

分析

  • 分析:LCS (Longest Common Subsequence) 算法
  1. 构造n×m的二维数组,其中n,m分别为s串和t串的长度。
  2. 双重循环嵌套,如果s串中的某个字符与t串中的某个字符相等,此时分为两种情况:
    ①此时相等的字符在二维数组中的第一行或第一列,则将arr[i][j]置为1;
    ②若是不满足①,则该位置的值为其上左对角线的值+1。即arr[i][j]=arr[i-1][j-1]+1。
  3. 二维数组中的最大值即为最长公共子串的长度,保存它的行、列下标值。
  4. 若是从s中截取公共子串,则用行号,行号-最长公共子串的长度即得到最长公共子串在s串中的起始下标值;若是从t中截取公共子串,则用列号,列号-最长公共子串的长度即得到最长公共子串在t串中的起始下标值。
    在这里插入图片描述

运行结果

在这里插入图片描述

  • 时间复杂度:O(nm) (主要用在给二维数组赋值)
  • 空间复杂度:O(nm) (创建了一个二维数组)
    其中n,m分别为s串和t串的长度。

方法二

  • 算法思想:采用Brute-Force(暴力)算法,也称简单匹配算法,扫描s串和t串。
SqString MaxComStr(SqString s, SqString t)
{SqString str;int i, j, count = 0, maxlen = 0, start;for (i = 0; i < s.length; i++){for (j = 0; j < t.length; j++){int start1 = i;int start2 = j;while ((start1 <= s.length - 1) && (start2 <= t.length - 1) && (s.data[start1] == t.data[start2])){start1++;start2++;count++;}if (count > maxlen){maxlen = count;start = i;}count = 0;}}printf("\n最长公共子串的起始下标为:%d 子串长度为:%d", start, maxlen);for (i = 0, j = start; i < maxlen; i++, j++)str.data[i] = s.data[j];str.length = maxlen;printf("\n最长公共子串为:");DispStr(str);return str;
}

分析

  1. 串s的data[0](字符’c’)逐个和串t的每个元素进行比较,第一轮没有相同的字符。
    在这里插入图片描述
  1. 串s的data[1](字符’o’)逐个和串t的每个元素进行比较
  • ①发现s的data[1]与t中data[9]的字符相等,此时再比较串s和串t的下一个字符(即s的data[2](字符‘n’)与t的data[10](字符’n’)进行比较)。
  • ②发现此时两个字符依然相等,再比较两个串的下一个字符(即s的data[3](字符‘v’)与t的data[11](字符’s’)进行比较)。此时发现两个字符不相等,此时串s和串t都不再后移。此轮中有两个字符相等,count=2。
  • ③由于①中s.data[1]与t.data[9]已经比较过了,此时让s.data[1](字符’o’)与t.data[10](字符’n’)比较,两个字符不相同,让s.data[1](字符’o’)与t.data[11](字符’s’)比较,两个字符依然不相同。此时t串已到末尾,此轮结束。
    在这里插入图片描述
  1. 串s的data[2](字符’n’)逐个和串t的每个元素进行比较
  • ①发现s的data[2]与t中data[1]的字符相等,此时再比较串s和串t的下一个字符(即s的data[3](字符‘v’)与t的data[2](字符’s’)进行比较)。此时发现两个字符不相等,串s和串t都不再后移。
  • ②由于①中s.data[2]与t.data[1]已经比较过了,此时让s.data[2](字符’n’)与t.data[2](字符’s’)比较,两个字符不相同,让s.data[2](字符’n’)与t.data[3](字符’t’)比较,两个字符依然不相同,继续此操作。
  • ③发现s的data[2]与t中data[10]的字符相等,此时再比较串s和串t的下一个字符(即s的data[3](字符‘v’)与t的data[11](字符’s’)进行比较)。此时发现两个字符不相等,串s和串t都不再后移。
  • ④由于③中s.data[2]与t.data[10]已经比较过了,此时让s.data[2](字符’n’)与t.data[11](字符’s’)比较,两个字符不相同,此时t串已到末尾,此轮结束。
    在这里插入图片描述

运行结果

在这里插入图片描述

  • 时间复杂度:O(nm) (BF算法的时间复杂度)
  • 空间复杂度:O(1) (除了str[ ],未开辟新空间)
    其中n,m分别为s串和t串的长度。

方法三

  • 算法思想:将较短串的头部和长串的尾部对齐,逐渐移动短串,比较重叠部分的字符串中的最长公共子串的长度,直到短串和长串的头部对齐。
SqString MaxComStr(SqString s, SqString t)
{SqString str;int i = 0, j, k, n = 1, m = s.length;int count = 0, index[2] = {0,0}, num = 0;while (n<=s.length) //循环的轮数由长串决定{i = --m; //每趟比较开始时,长串的起始下标j = 0; //每趟比较开始,短串的起始下标都为0if (i > s.length - t.length) k = s.length - i; //如果短串尾和长串尾没对其,每趟中需要比较的次数依次递增else k=t.length; //一旦短串尾和长串尾对其,之后的每一趟,比较次数都由短串决定while (k > 0) //k为每趟中需要比较次数{if (s.data[i] == t.data[j]) count++; //如果纵向对应的两个字符相等,公共子串的长度+1i++; j++; //依次比较每个纵向对应的两个字符if (s.data[i-1]==t.data[j-1]&&s.data[i] != t.data[j]) //如果前一个纵向的两个字符相等,但下一个纵向的两个字符不相等,即字符不连续相等{if (count > num) //记录最长的连续公共字符{num = count; //记录断开前的最长公共子串的长度index[0] = i - count;  //最长公共子串在长串中的起始下标index[1] = index[0] - m; //最长公共子串在短串中的起始下标}count = 0; //每次发现字符不连续相等时,需要重新计数,然后再比较断开前后的长度,取最大值}k--; //每比较一次,剩余需要比较的次数减少}n++; //比较的总趟数减1}for (i = 0, j = index[0]; i < num; i++, j++)str.data[i] = s.data[j];str.length = num;printf("\n最长公共子串在长串中的起始下标为:%d\n在短串中的起始下标为:%d\n最长公共字串的长度为:%d", index[0], index[1], num);printf("\n最长公共子串为:");DispStr(str);return str;
}

分析

  • 该算法只需纵向比较对应位置即可,斜对角的位置可不用比较。
  • 如在图中,第二轮比较s.data[12]==t.data[0]?、s.data[13]==t.data[1]?
  • 而s.data[12]==t.data[1]?会在下一轮中进行比较;而s.data[13]==t.data[0]?已经在上一轮中比较完毕。
    在这里插入图片描述
  • 从图中可以分析出,总共需要比较14轮(14为较长串的长度),而最坏情况下,第一轮比较1次,第二轮比较2次…第十二轮比较12次(此时两个串尾对齐)。此时离14轮还差两轮,而剩下的两轮比较的次数都是12(即较短串的长度)。

实现步骤:

  1. 定义一个变量n(用来表示总共需要比较多少轮),比较的轮数由长串的长度决定。每比较一轮,让n++。
  2. 定义一个变量m(用来表示每一轮中,长串开始比较的起始下标),m的初始值为长串的长度,每一轮中先让m减1之后再开始比较。
  3. 定义两个变量i(表示每次比较时,长串的起始下标),j(表示每次比较时,短串的起始下标),从图中可以看出,j每次都是比较的范围都是0~m-1,而i可以表示为–m。
  4. 定义变量k(用来表示每轮循环需要比较次数),每轮比较开始,k=m,每一轮中每比较一次,k–。
  5. 定义变量count(用来统计每轮中重复字符的个数)
  6. 定义变量num(最长公共子串的长度)和index(最长公共子串的起始下标)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

运行结果

在这里插入图片描述

  • 时间复杂度: 1 + 2 + . . . + n + ( n − m ) m 1+2+...+n+(n-m)m 1+2+...+n+(nm)m
  • 空间复杂度:O(1)
    其中n,m分别为s串和t串的长度。

http://chatgpt.dhexx.cn/article/Di7k17h1.shtml

相关文章

常考的经典算法--最长公共子序列(LCS)与最长公共子串(DP)

《1》最长公共子序列&#xff08;LCS&#xff09;与最长公共子串(DP) http://blog.csdn.net/u012102306/article/details/53184446 https://segmentfault.com/a/1190000007963594 http://www.cppblog.com/mysileng/archive/2013/05/14/200265.html 1. 问题描述 子串应该比…

LCS算法学习

LCS的定义 最长公共子序列&#xff0c;即Longest Common Subsequence&#xff0c;LCS。一个序列S任意删除若干个字符得到新序列T&#xff0c;则T叫做S的子序列&#xff1b;两个序列X和Y的公共子序列中&#xff0c;长度最长的那个&#xff0c;定义为X和Y的最长公共子序列。 字符…

算法系列之六:最长公共子序列(LCS)问题(连续子序列)的三种解法

最长公共子序列&#xff08;LCS&#xff09;问题有两种方式定义子序列&#xff0c;一种是子序列不要求不连续&#xff0c;一种是子序列必须连续。上一章介绍了用两种算法解决子序列不要求连续的最终公共子序列问题&#xff0c;本章将介绍要求子序列必须是连续的情况下如何用算法…

Hirschberg的LCS算法实现

解决Longest Common Subsequence(LCS)问题最常用的算法是Dyanmic programing&#xff0c;细节可以参考Ch15.4 of Introduction of Algorithm(2ED), MIT press, p 350。这个算法最大的问题是他的空间复杂度是O(m*n)。这样&#xff0c;当两个序列达到上万个节点时&#xff0c;内存…

SLIC算法

基础知识 在介绍SLIC之前&#xff0c;先来介绍以下Lab颜色空间的介绍。 Lab色彩模型是由亮度(L)要素和与有关色彩的a,b要素组成&#xff0c;L的值由0(黑色)到100(白色)&#xff0c;a表示从洋红色至绿色的范围(a为负值表示绿色而正值表示品红)&#xff0c;b表示从黄色至蓝色的…

动态规划之LCS算法

一、前言 LCS是Longest Common Subsequence的缩写&#xff0c;即最长公共子序列。一个序列&#xff0c;如果是两个或多个已知序列的子序列&#xff0c;且是所有子序列中最长的&#xff0c;则为最长公共子序列。 另外还有个分支问题&#xff1a;最长公共子串。子串的字符位置必…

LCS算法的C++实现

这两天忙里偷闲看了July的团队提供的LCS算法视频&#xff0c;真的如视频标题一样&#xff0c;十分钟搞定LCS算法。 感谢July大神&#xff0c;感谢其团队的邹博。 这里附上视频链接&#xff1a;http://www.julyedu.com/video/play?course17 说是十分钟搞定&#xff0c;其实是…

算法学习 - 最长公共子序列(LCS)C++实现

最长公共子序列 最长公共子序列的问题很简单&#xff0c;就是在两个字符串中找到最长的子序列&#xff0c;这里明确两个含义&#xff1a; 子串&#xff1a;表示连续的一串字符 。子序列&#xff1a;表示不连续的一串字符。 所以这里要查找的是不连续的最长子序列&#xff0c; …

SLIC算法介绍

SLIC&#xff08;simple linear iterativeclustering&#xff09;&#xff0c;即 简单线性迭代聚类 。 &#x1f49b;它是2010年提出的一种思想简单、实现方便的算法&#xff0c;将彩色图像转化为CIELAB颜色空间和XY坐标下的5维特征向量&#xff0c;然后对5维特征向量构造距离度…

LSC算法

1.问题 给定序列 X<x_1,x_2,…,x_m> Y<y_1,y_2,…,y_j> 求X和Y的最长公共子序列(LCS) 2.解析 X<x1,x2,x3,x4…,xi> Y<y1,y2,y3,y4…,yi> 如果Z<z1,z2,z3,z4…,zk>是他们的最长公共子序列 则&#xff1a; &#xff08;1&#xff09;xi yi&…

LCS算法详解

程序员编程艺术第十一章&#xff1a;最长公共子序列(LCS)问题 0、前言 程序员编程艺术系列重新开始创作了&#xff08;前十章&#xff0c;请参考程序员编程艺术第一~十章集锦与总结&#xff09;。回顾之前的前十章&#xff0c;有些代码是值得商榷的&#xff0c;因当时的代码只顾…

LCS 最大公共序列算法

这些天在了解chrome的courgette, 了解了rsync算法, 也了解了courgette使用了bsdiff 算法, 然后知道了bsdiff算法里主要使用的是 LCS 算法, 这里参考了july大牛的文章: http://blog.csdn.net/v_july_v/article/details/6695482 自己做一点概括性的总结, 用以备忘, 也把自…

最长公共子序列(LCS)算法

一、最长公共字串与最长公共子序列 最长公共子串&#xff08;Longest Common Substirng&#xff09; 子串是串的一个连续的部分&#xff0c;子串中字符的位置必须连续。 例如&#xff1a;有两个字符串ABCBDAB 和 BDCABA&#xff0c;则它们的最长公共子串是&#xff1a;AB。 …

LCS(longest common sequence)算法的实现(十分详细)

一、问题描述 有两个字符串&#xff0c;求二者的最长公共子序列。 最长公共子序列&#xff1a;不必连续但必须有序的子列&#xff08;与子串区分&#xff0c;子串是连续的&#xff09; 二&#xff1a;解决方法 第一种方法&#xff1a;穷举法 &#xff0c;就是一个一个的对比&a…

LCS算法

LCS算法 LCS算法&#xff1a; LCS是Longest Common Subsequence的缩写&#xff0c;即最长公共子序列。一个序列&#xff0c;如果是两个或多个已知序列的子序列&#xff0c;且是所有子序列中最长的&#xff0c;则为最长公共子序列。LCS不是唯一的&#xff0c;它可以有很多种&am…

Oracle中索引的原理

索引的概念 索引是一种数据库结构&#xff0c;能够就数据库中的某列提供快速查询&#xff0c;而不用检索整个表格&#xff08;官方的不行&#xff09;。 在 Oracle 数据库中&#xff0c;存储的每一行数据都有一个 rowID 来标识。当 Oracle 中存储着大量的数据时&#xff0c;意…

MongoDB索引原理及实践

背景 数据库的演进 随着计算机的发展&#xff0c;越来越多的数据需要被处理&#xff0c;数据库是为处理数据而产生。从概念上来说&#xff0c;数据库是指以一定的方式存储到一起&#xff0c;能为多个用户共享&#xff0c;具有更可能小的冗余&#xff0c;与应用程序彼此独立的…

MySql存储引擎和索引原理

转载 https://blog.csdn.net/tongdanping/article/details/79878302 注意&#xff1a; 1、索引需要占用磁盘空间&#xff0c;因此在创建索引时要考虑到磁盘空间是否足够 2、创建索引时需要对表加锁&#xff0c;因此实际操作中需要在业务空闲期间进行 MySQL支持诸多存储引擎&a…

MySQL之索引原理

1 简介 索引底层就是一种数据结构&#xff0c;空间换时间&#xff0c;能够帮助我们快速定位到对应的数据&#xff0c;就类似于字典里面的目录一样。 索引虽然能快速检索数据&#xff0c;但会影响数据修改的操作&#xff0c;而且索引存储在具体的文件&#xff0c;占用一定的空…

深入浅出数据库索引原理

使用索引很简单&#xff0c;只要能写创建表的语句&#xff0c;就肯定能写创建索引的语句&#xff0c;要知道这个世界上是不存在不会创建表的服务器端程序员的。然而&#xff0c; 会使用索引是一回事&#xff0c; 而深入理解索引原理又能恰到好处使用索引又是另一回事&#xff0…