最长公共子序列

寻找两个给定序列的子序列,该子序列在两个序列中以相同的顺序出现,但是不必要是连续的

举例:X=ABCBDAB,Y=BDCABA。序列 BCA是X和Y的一个公共子序列,但不是X和Y的最长公共子序列,子序列BCBA是X和Y的一个LCS,序列BDAB也是

解法:动态规划是为了降低时间复杂度的一种算法,申请一个额外空间,来保存每一个步骤的结果,最后从这些结果中找到最优的解。一般来说,当前的最优解,只与当前时刻和上一时刻有关系,和其他时刻没有关系,这样才能让动态规划发生作用,降低复杂度。(DP动态规划最终处理的还是数值(极值做最优解),找到了最优值,就找到了最优方案)。

定义dp[i][j]记录序列LCS的长度,用i和j分别表示序列X的长度和序列Y的长度,状态转移方程为

  • dp[i][j] = 0  如果i=0或j=0
  • dp[i][j] = dp[i-1][j-1] + 1  如果X[i-1] = Y[i-1]
  • dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }  如果X[i-1] != Y[i-1]

动态规划和滚动数组优化空间。

1、输出所有最长公共子序列

解法:动态规划dp解法:空间和时间O(m*n)

#include <vector>
#include <set>
#include <string>
#include <iostream>
#include <sstream>
using namespace std;set<string> all_lcs; //注意这里要用set去除重复的LCS
//二维数组veca[i][j]记录的是两个字符串Xi和Yj的LCS长度
int LCS_length(const string &str1, const string &str2, vector<vector<int> > &veca) {int i, j;if (str1 == "" || str2 == "")return 0;for (i = 0; i <= str1.length(); i++) {//侧面的一行和一列初始化为0veca[i][0] = 0;}for (j = 0; j <= str2.length(); j++) {veca[0][j] = 0;}for (i = 1; i <= str1.length(); i++) {for (j = 1; j <= str2.length(); j++) {if (str1[i - 1] == str2[j - 1]) {veca[i][j] = veca[i - 1][j - 1] + 1;}else {if (veca[i - 1][j] >= veca[i][j - 1])veca[i][j] = veca[i - 1][j];elseveca[i][j] = veca[i][j - 1];}}}return veca[str1.length()][str2.length()];
}//该函数找出所有的LCS的序列,并将其存在vector中
void PrintAllLCS(string &str1, string &str2, int i, int j,vector<vector<int> > &veca, string lcs_str) {//注意这里形参lcs_str不可以为引用,这里需要每次调用lcs_str都重新生成一个对象while (i > 0 && j > 0) {if (str1[i - 1] == str2[j - 1]) {lcs_str = str1[i - 1] + lcs_str; //逆向存放--i;--j;}else {if (veca[i - 1][j] > veca[i][j - 1]) //向左走--i;else if (veca[i - 1][j] < veca[i][j - 1]) //向上走--j;else { //此时向上向右均为LCS的元素PrintAllLCS(str1, str2, i - 1, j, veca, lcs_str);PrintAllLCS(str1, str2, i, j - 1, veca, lcs_str);return;}}}cout << "   " << lcs_str << endl;all_lcs.insert(lcs_str);
}
int main() {string input;getline(cin, input);//读入一行,默认遇到\n回车时停止
    stringstream ss(input);string str1, str2;ss >> str1;//遇到空格停止ss >> str2; //空格后赋值给str2//将veca初始化为一个二维数组,其行列值分别为str1和str2的长度加1//二维数组veca记录的是两个字符串Xi和Yj的LCS长度vector<vector<int> > veca(str1.length() + 1, vector<int>(str2.length() + 1));cout << LCS_length(str1, str2, veca) << endl;//输出string lcs_str;PrintAllLCS(str1, str2, str1.length(), str2.length(), veca, lcs_str);set<string>::iterator iter = all_lcs.begin();while (iter != all_lcs.end()) {cout << *iter++ << endl;}return 0;
}

 2、优化:滚动数组

优化空间变O(N),时间还是O(m*n),但此时无法输出子序列

#include <vector>
#include <set>
#include <string>
#include <iostream>
#include <sstream>
using namespace std;//二维数组veca[i][j]记录的是两个字符串Xi和Yj的LCS长度
int LCS_length(const string &str1, const string &str2, vector<vector<int> > &veca) {int i, j;if (str1 == "" || str2 == "")return 0;//侧面的一行和一列初始化为0veca[1][0] = 0;for (j = 0; j <= str2.length(); j++) {veca[0][j] = 0;}int k = 0;for (i = 1; i <= str1.length(); i++) {k = i & 1;for (j = 1; j <= str2.length(); j++) {if (str1[i - 1] == str2[j - 1]) {veca[k][j] = veca[k^1][j - 1] + 1;}else {if (veca[k^1][j] >= veca[k][j - 1])veca[k][j] = veca[k^1][j];elseveca[k][j] = veca[k][j - 1];}}}return veca[k][str2.length()];
}int main() {string input;getline(cin, input);//读入一行,默认遇到\n回车时停止
    stringstream ss(input);string str1, str2;ss >> str1;//遇到空格停止ss >> str2;vector<vector<int> > veca(2, vector<int>(str2.length() + 1)); //只有2行,交替存储cout << LCS_length(str1, str2, veca) << endl;return 0;
}

另一个只能输出一个最长公共子序列

最长公共子串

子串需要连续

dp[i][j]表示以x[i]和y[j]结尾的最长公共子串的长度。

因为要求子串连续,所以对于X[i]与Y[j]来讲,它们要么与之前的公共子串构成新的公共子串;要么就是不构成公共子串。故状态转移方程

  • X[i] == Y[j],dp[i][j] = dp[i-1][j-1] + 1
  • X[i] != Y[j],dp[i][j] = 0

对于初始化,i==0或者j==0,如果X[i] == Y[j],dp[i][j] = 1;否则dp[i][j] = 0。

解法:动态规划、滚动数组优化空间、后缀数组优化时间和空间

1、输出多个最长公共子串

dp方法,时间空间都是O(m*n)。

2、优化

可按照最长公共子序列方法引入滚动数组优化空间On,但此时无法输出具体子串。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;int maxindex[30];
int maxlen;    /* 记录最大公共子串长度 */
//int maxindex;  /* 记录最大公共子串在串1的起始位置 */void outputLCS(char * X)
{if (maxlen == 0){printf("NULL LCS\n");return;}printf("The len of LCS is %d\n", maxlen);int i = 0;while (maxindex[i] != 0) {int k = maxlen;while (k--){printf("%c", X[maxindex[i]++]);}printf("\n");i++;}
}int dp[30][30];
void LCS_dp(char * X, int xlen, char * Y, int ylen)
{//maxlen = maxindex = 0;int k = 0;for (int i = 0; i < xlen; ++i){for (int j = 0; j < ylen; ++j){if (X[i] == Y[j]){if (i && j){dp[i][j] = dp[i - 1][j - 1] + 1;}if (i == 0 || j == 0){dp[i][j] = 1;}if (dp[i][j] > maxlen){maxlen = dp[i][j];memset(maxindex, 0, 30);k = 0;maxindex[k++] = i + 1 - maxlen;}else if (dp[i][j] == maxlen) {maxindex[k++] = i + 1 - maxlen;}}}}outputLCS(X);
}void main()
{char X[] = "aaababbb";char Y[] = "abadbbb";/* DP算法 */LCS_dp(X, strlen(X), Y, strlen(Y));
}

只能输出一个最长公共子串

3、优化:后缀数组

字符串X的长度为m,Y的长度为n,最长公共子串长度为l,时间复杂度为O((m+n)*l*lg(m+n)),空间复杂度为O(m+n)

思路:由于后缀数组最典型的是寻找一个字符串的重复子串,所以,对于两个字符串,我们可以将其连接到一起,如果某一个子串s是它们的公共子串,则s一定会在连接后字符串后缀数组中出现两次,这样就将最长公共子串转成最长重复子串的问题了

注意:在找到两个重复子串时,不一定就是X与Y的公共子串,也可能是X或Y的自身重复子串,故在连接时候我们在X后面插入一个特殊字符‘#’,即连接后为X#Y。

注意:要保证找到公共子串后二者只有一个#号,这样才表示来自不同的字符串

后缀数组输出多个最长公共子串

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;int maxlen;    /* 记录最大公共子串长度 */
//int maxindex;  /* 记录最大公共子串在串1的起始位置 */
int suff_index_vec[100];
char * suff[100];
void outputLCS()
{if (maxlen == 0){printf("NULL LCS\n");return;}printf("The len of LCS is %d\n", maxlen);int i = 0;int j = 0;while (suff_index_vec[i++] != 0) {int k = maxlen;while (k--){printf("%c", suff[suff_index_vec[i]][j++]);//针对某一个后缀字符串,从头开始输出maxlen个
        }printf("\n");}}//char * suff[100];int pstrcmp(const void *p, const void *q)
{return strcmp(*(char**)p, *(char**)q);
}int comlen_suff(char * p, char * q)
{   //要保证找到公共子串后二者只有一个#号,这样才表示来自不同的字符串int len = 0;while (*p && *q && *p++ == *q++){++len;if (*p == '#' || *q == '#'){  //遇到#需要break,否则就是同一(连接的后一个)字符串内匹配break;}}int count = 0;while (*p){if (*p++ == '#'){++count;break;}}while (*q){if (*q++ == '#'){++count;break;}}if (count == 1)  //只有一个#,说明一个后缀在#前,一个后缀在#后return len;return 0;
}void LCS_suffix(char * X, int xlen, char * Y, int ylen)
{int len_suff = xlen + ylen + 1;char * arr = new char[len_suff + 1];  /* 将X和Y连接到一起 */strcpy(arr, X);arr[xlen] = '#';strcpy(arr + xlen + 1, Y);for (int i = 0; i < len_suff; ++i)  /* 初始化后缀数组 */{suff[i] = &arr[i];}qsort(suff, len_suff, sizeof(char *), pstrcmp);//int suff_index_vec[100];int k = 0;for (int i = 0; i < len_suff - 1; ++i){int len = comlen_suff(suff[i], suff[i + 1]);if (len > maxlen){maxlen = len;memset(suff_index_vec, 0, 100);k = 0;suff_index_vec[k++] = i;//suf_index = i;
        }else if (len == maxlen) {suff_index_vec[k++] = i;}}outputLCS();
}void main()
{char X[] = "aaababbb";char Y[] = "abaebbb";/* 后缀数组方法 */LCS_suffix(X, strlen(X), Y, strlen(Y));
}

只能输出一个最长公共子串

最长可重叠重复子串

bnanana的最长可重叠重复子串Longest Repeat Substring是ana。

解法:基本方法和后缀数组优化空间和时间

以下都只输出一个。

1、基础方法

时间On2

#include<cstdio>
#include<string>
using namespace std;int maxlen;    /* 记录最长重复子串长度 */
int maxindex;  /* 记录最长重复子串的起始位置 */int comlen(char * p, char * q)
{ //比较重叠长度。输入是指针,然后对字符串顺序比对int len = 0;while (*p && *q && *p++ == *q++){++len;}return len;
}void outputLRS(char * arr)
{  //输出if (maxlen == 0){printf("NULL LRS\n");return;}printf("The len of LRS is %d\n", maxlen);int i = maxindex;while (maxlen--){printf("%c", arr[i++]);}printf("\n");
}void LRS_base(char * arr, int size)
{for (int i = 0; i < size; ++i){for (int j = i + 1; j < size; ++j){int len = comlen(&arr[i], &arr[j]);if (len > maxlen){maxlen = len;maxindex = i;}}}outputLRS(arr);
}void main()
{char X[] = "banana";/* 基本算法 */LRS_base(X, strlen(X));
}

2、优化:后缀数组

时间On

#include<cstdio>
#include<string>
using namespace std;int maxlen;    /* 记录最长重复子串长度 */
int maxindex;  /* 记录最长重复子串的起始位置 */int comlen(char * p, char * q)
{ //比较重叠长度。输入是指针,然后对字符串顺序比对int len = 0;while (*p && *q && *p++ == *q++){++len;}return len;
}void outputLRS(char * arr)
{  //输出if (maxlen == 0){printf("NULL LRS\n");return;}printf("The len of LRS is %d\n", maxlen);int i = maxindex;while (maxlen--){printf("%c", arr[i++]);}printf("\n");
}char * suff[30];  //数组元素都是指针int pstrcmp(const void * p, const void * q)
{//p指向suff[0],需要先char**让suff[0]指向是char,然后*取suff[0]的值,即一个指针return strcmp(*(char**)p, *(char**)q);  //p大,返回大于0,则需要交换
}void LRS_suffix(char * arr, int size)
{int suff_index = maxlen = maxindex = 0;for (int i = 0; i < size; ++i) /* 初始化后缀数组,每个里面放arr元素一个指针*/{suff[i] = &arr[i];}qsort(suff, size, sizeof(char *), pstrcmp); /* 字典序排序后缀数组 */for (int i = 0; i < size - 1; ++i)  /* 寻找最长重复子串 */{int len = comlen(suff[i], suff[i + 1]);if (len > maxlen){maxlen = len;suff_index = i;}}outputLRS(suff[suff_index]);
}void main()
{char X[] = "banana";/* 后缀数组方法 */LRS_suffix(X, strlen(X));
}

其中字典序排序结束的后缀数组suff

suff[0]  a

suff[1]  a  但放入comlen中会*p++,即为ana

suff[2]  anana

suff[3]  banana

suff[4]  na

suff[5]  nana

转载于:https://www.cnblogs.com/beixiaobei/p/10599013.html

最大公共子序列、子串、可重叠重复子串相关推荐

  1. 最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和...

    最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和 文章作者:Yx.Ac   文章来源:勇幸|Thinking (http://www.ahathi ...

  2. poj 1743 二分答案+后缀数组 求不重叠的最长重复子串

    题意:给出一串序列,求最长的theme长度 (theme:完全重叠的子序列,如1 2 3和1 2 3  or  子序列中每个元素对应的差相等,如1 2 3和7 8 9) 要是没有差相等这个条件那就好办 ...

  3. 最长可重叠的重复子串(2)

    http://www.ahathinking.com/archives/121.html 文章作者: Yx.Ac   文章来源: 勇幸|Thinking ( http://www.ahathinkin ...

  4. 最长重复子串和最长不重复子串求解

    最长重复子串和最长不重复子串求解 本文内容框架: §1 最长重复子串 基本方法.KMP算法求解.后缀数组求解 §2 最长不重复子串 基本方法.动态规划.动态规划+Hash §3 小结 §1最长重复子串 ...

  5. 递归法:求两个串的最大公共子序列的长度

    问题:求两个串的最大公共子序列的长度 举例: 子串: abcgxs 与sabxfh 其最大公共子序列的为abx,长度为3 public class Zixulie {public static int ...

  6. #1407 : 后缀数组二·重复旋律2 (不可重叠最长重复子串问题)

    题目链接 思路 求不可重叠最长重复子串,也可以利用height[i]height[i]height[i]. 二分枚举答案KKK 当height[i]≥Kheight[i] \ge Kheight[i] ...

  7. 后缀数组--(可重叠最长重复子串问题)

    问题描述:给定一个字符串,求最长重复子串,这两个子串可以重叠. 其实问题可以转化为height数组的最大值.至于为什么是这样,我可以这样解释: 求可重叠最长重复子串等价于求两个后缀的最长公共前缀的最大 ...

  8. POJ 1743 (后缀数组+不重叠最长重复子串)

    题目链接: http://poj.org/problem?id=1743 题目大意:楼教主の男人八题orz.一篇钢琴谱,每个旋律的值都在1~88以内.琴谱的某段会变调,也就是说某段的数可以加减一个旋律 ...

  9. 数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串 (转)...

    作者:寒小阳 时间:2013年9月. 出处:http://blog.csdn.net/han_xiaoyang/article/details/11969497. 声明:版权所有,转载请注明出处,谢谢 ...

最新文章

  1. Linux下tcpdump用法
  2. 非科班出身获得软开职位
  3. 【HDU2683 TCE-frep number system 完全数+二项展开式】
  4. Docker 安装JDK1.8
  5. 关于烂代码的那些事——什么是好代码
  6. [深入学习C#]利用反射给对象赋值
  7. 地产相继入局智能家居,LifeSmart云起获新世界集团战略投资
  8. linux列举网卡,linux下快速列出局域网中所有主机名(计算机名)的脚本
  9. 2018年全球智能手机销售收入增至5220亿美元 但销量却下降了
  10. 死磕shell系列-shell介绍
  11. 简述mysql事件作用_MYSQL使用简述
  12. 《Spring微服务实战》读书笔记——构建微服务
  13. 程序员必知3大查找(转)
  14. 1)hadoop集群搭建
  15. dict后缀_词根词缀法记单词之dict
  16. iOS DevCamp Android DevCamp 课程集锦 为最喜爱的课程投票 获得CSDN社区会员专享特惠票...
  17. 网页百度网盘上传显示服务器错误,win7系统下登陆百度浏览器提示连接服务器错误的方案?...
  18. 协同感知综述:从异质单体到分层合作
  19. 常见的软件系统集成方式和Smartbi集成解决方案
  20. 交通灯系统51单片机设计(附Proteus仿真、C程序、原理图及PCB、论文等全套资料)

热门文章

  1. 处理器后面的字母含义_电脑天天用,但CPU后缀的一个字母你知道代表这什么吗?...
  2. kali 树莓派 android,【原】树莓派安装KALI LINUX的手记
  3. json为全局变量 vue_vue package.json设置全局变量
  4. java定义全局变量_矮油,你知道什么是 Java变量的作用域 嘛?
  5. 我的职业发展目标计算机作文,作文《我的职业发展目标》1000字
  6. mysql的count(*)的优化,获取千万级数据表的总行数
  7. python【蓝桥杯vip练习题库】ADV-186排列式
  8. python【Matlibplot绘图库】利用matlibplot绘制雷达图
  9. oracle clob raw 转换,ORA-22835 缓冲区对于 CLOB 到 CHAR 转换或 BLOB 到 RAW 转换而言太小...
  10. c# 指定打开某个路径下的CMD_(win10下sublime通过配置JSON调用MATLAB直接运行程序)(转载)...