动态规划之最长公共子序列
2016-07-18 15:52
441 查看
面试经常出现问题:
一.最长公共子序列的定义
子序列:若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij.
公共子序列:给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列.
最长公共子序列:给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列.
如:序列ABCDEF和ADFGH的最长公共子序列为ADF
注意:最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,简称LCS)的区别为是最长公共子串的串是一个连续的部分,而最长公共子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;通俗的说就是子串中字符的位置必须是连续的而子序列则可以不必连续.
二.最优子结构性质
设序列X={x1,x2,…,xm}和Y={y1,y2,…,yn}的最长公共子序列为Z={z1,z2,…,zk} ,则
(1)若xm=yn,则zk=xm=yn,且z1,z2,…, zk-1是否为x1,x2,…,xm-1和y1,y2,…,yn-1的最长公共子序列.
(2)若xm≠yn且zk≠xm,则Z是x1,x2,…,xm-1和Y的最长公共子序列.
(3)若xm≠yn且zk≠yn,则Z是X和y1,y2,…,yn-1的最长公共子序列.
由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列.因此,最长公共子序列问题具有最优子结构性质.当问题具有最优子结构性质和子问题重叠性质时就可以用动态规划算法解决该问题.
三.动态规划方法分析
由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系.用c[i][j]记录序列和的最长公共子序列的长度.其中,Xi={x1,x2,…,xi},Yj={y1,y2,…,yj}.当i=0或j=0时,空序列是Xi和Yj的最长公共子序列.故此时C[i][j]=0.其它情况下,由最优子结构性质可建立递归关系如下:
例如:输入字符串“bdcaba”和"abcbdab",求它们的最长公共子序列长度.在《算法设计与分析》课程中我们老师讲述的方法通常是使用动态规划填充表格方法解决.初始时,X字符串的长度为m,Y字符串的长度为n.c[m,n]二位数组如上面递归关系递归,最后的c[m,n]为最大数字即最长公共子序列的长度.
其对应的核心代码如下:
四。从表中找出最长公共子序列的方法
(1) 从(m,n)到(0,0)
(2) 若当前格与左边一格相同,则画" 一";若当前格与上边一格相同,则画"|";上两者都不符合,从当前格到左上格化斜线箭头"\";
(3) 从当前格向箭头方向前进一格,对此格进行(2)
(4) 从(m,n)到(0,0)的不同路径中,斜线箭头"\"相对应的格的元素构成最长公共子序列.如图bcbd、bcdb、badb.
五.问题的升华与解决
1.升华问题
输入:输入文件中的第1行是一个正整数T(0<T<=10),表示有T组测试数据.接下来是每组测试数据的描述,每组测试数据有3行.测试数据的第1行有2个正整数m、n,中间用一个空格隔开(0<m,n<50);第2、3行是长度分别为m、n的2个序列X和Y,每个序列的元素间用一个空格隔开.序列中每个元素由字母、数字等构成.输入直到文件结束
输出:对输入中的每组测试数据,先输出Case #表示第几组数据,在输出最长公共子序列,输出所有的最长公共子序列,并输出动态规划表格c表和b表.(测试用例见结果图)
2.代码
这里涉及到一个新的问题:就是使用上面所叙述的填充表格来实现动态规划,其中c[m,n]记录的是当前序列的最长子序列长度;还需要引用一个吧b[m,n]表来寻找所有最长公共子序列,并把结果存入到result[]数组中.其中最重要的代码就是两个实现的函数,如下:
第一个LSCLength函数是求最长公共子序列长度的函数,并在该函数中填充c[m]
和b[m]
.
由于有时并不是只有一个最长公共子序列,所以,对上面的代码进行改进,增加一个数组保存结果等....代码如下所示。
所有情况代码:
最大子序列:http://blog.csdn.net/yangquanhui1991/article/details/51943359
动态规划之最长递增子序列:http://blog.csdn.net/yangquanhui1991/article/details/51943000
动态规划之最长公共子串:http://blog.csdn.net/yangquanhui1991/article/details/51945211
动态规划之最长公共子序列:http://blog.csdn.net/yangquanhui1991/article/details/51942532
一.最长公共子序列的定义子序列:若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij.
公共子序列:给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列.
最长公共子序列:给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列.
如:序列ABCDEF和ADFGH的最长公共子序列为ADF
注意:最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,简称LCS)的区别为是最长公共子串的串是一个连续的部分,而最长公共子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;通俗的说就是子串中字符的位置必须是连续的而子序列则可以不必连续.
二.最优子结构性质
设序列X={x1,x2,…,xm}和Y={y1,y2,…,yn}的最长公共子序列为Z={z1,z2,…,zk} ,则
(1)若xm=yn,则zk=xm=yn,且z1,z2,…, zk-1是否为x1,x2,…,xm-1和y1,y2,…,yn-1的最长公共子序列.
(2)若xm≠yn且zk≠xm,则Z是x1,x2,…,xm-1和Y的最长公共子序列.
(3)若xm≠yn且zk≠yn,则Z是X和y1,y2,…,yn-1的最长公共子序列.
由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列.因此,最长公共子序列问题具有最优子结构性质.当问题具有最优子结构性质和子问题重叠性质时就可以用动态规划算法解决该问题.
三.动态规划方法分析
由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系.用c[i][j]记录序列和的最长公共子序列的长度.其中,Xi={x1,x2,…,xi},Yj={y1,y2,…,yj}.当i=0或j=0时,空序列是Xi和Yj的最长公共子序列.故此时C[i][j]=0.其它情况下,由最优子结构性质可建立递归关系如下:
例如:输入字符串“bdcaba”和"abcbdab",求它们的最长公共子序列长度.在《算法设计与分析》课程中我们老师讲述的方法通常是使用动态规划填充表格方法解决.初始时,X字符串的长度为m,Y字符串的长度为n.c[m,n]二位数组如上面递归关系递归,最后的c[m,n]为最大数字即最长公共子序列的长度.
其对应的核心代码如下:
//参数:x字符串长度为m y字符串长度为n void LCSLength(char x[], char y[],int m,int n) { /* 计算最长公共子序列的长度 */ int L[m] ,i,j; for (i = 0; i <= m; i++) L[i][0] = 0; for (i = 0; i <= n; i++) L[0][i] = 0; for (i = 1; i <= m; i++) { for (j = 1; j <= n; j++) { if (x[i]==y[j]) L[i][j]=L[i-1][j-1]+1; else if (L[i-1][j]>= L[i][j-1]) L[i][j]= L[i-1][j]; else L[i][j]= L[i][j-1]; } } return L[m] ; }
四。从表中找出最长公共子序列的方法
(1) 从(m,n)到(0,0)
(2) 若当前格与左边一格相同,则画" 一";若当前格与上边一格相同,则画"|";上两者都不符合,从当前格到左上格化斜线箭头"\";
(3) 从当前格向箭头方向前进一格,对此格进行(2)
(4) 从(m,n)到(0,0)的不同路径中,斜线箭头"\"相对应的格的元素构成最长公共子序列.如图bcbd、bcdb、badb.
五.问题的升华与解决
1.升华问题
输入:输入文件中的第1行是一个正整数T(0<T<=10),表示有T组测试数据.接下来是每组测试数据的描述,每组测试数据有3行.测试数据的第1行有2个正整数m、n,中间用一个空格隔开(0<m,n<50);第2、3行是长度分别为m、n的2个序列X和Y,每个序列的元素间用一个空格隔开.序列中每个元素由字母、数字等构成.输入直到文件结束
输出:对输入中的每组测试数据,先输出Case #表示第几组数据,在输出最长公共子序列,输出所有的最长公共子序列,并输出动态规划表格c表和b表.(测试用例见结果图)
2.代码
这里涉及到一个新的问题:就是使用上面所叙述的填充表格来实现动态规划,其中c[m,n]记录的是当前序列的最长子序列长度;还需要引用一个吧b[m,n]表来寻找所有最长公共子序列,并把结果存入到result[]数组中.其中最重要的代码就是两个实现的函数,如下:
第一个LSCLength函数是求最长公共子序列长度的函数,并在该函数中填充c[m]
和b[m]
.
//字符串处理 int LCS_LENGTH(const char* str1, const char* str2,int c[][100],int b[][100]) { if ((str1 == NULL) || (str2 == NULL)) return 0; int m = strlen(str1); int n = strlen(str2); for (int i = 1; i < m; i++) c[i][0] = 0; for (int i = 1; i < n; i++) c[0][i] = 0; c[0][0] = 0; for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (str1[i-1] == str2[j-1]) { c[i][j] = c[i - 1][j - 1] + 1; b[i][j] = 1; } else if (c[i - 1][j] >= c[i][j - 1]) { c[i][j] = c[i - 1][j]; b[i][j] = 2; } else { c[i][j] = c[i][j - 1]; b[i][j] = 3; } cout << "计算最优值效果图如下所示:" << endl; for (int i = 0; i <= m; i++) { for (int j = 0; j <= n; j++) { cout << c[i][j] << " "; } cout << endl; } return c[m] ; } void print_LCS(int b[][100], const char* str, int i, int j) { if ((i == 0) || (j == 0)) return; if (b[i][j] == 1) { print_LCS(b, str, i - 1, j - 1); cout << str[i - 1]; } else if (b[i][j] == 2) print_LCS(b, str, i - 1, j); else print_LCS(b, str, i, j - 1); }
由于有时并不是只有一个最长公共子序列,所以,对上面的代码进行改进,增加一个数组保存结果等....代码如下所示。
//求取所有的最长公共子序列 #include <iostream> using namespace std; const int X = 100, Y = 100; //串的最大长度 char result[X+1]; //用于保存结果 int count=0; //用于保存公共最长公共子串的个数 /*功能:计算最优值 *参数: * x:字符串x * y:字符串y * b:标志数组 * xlen:字符串x的长度 * ylen:字符串y的长度 *返回值:最长公共子序列的长度 * */ int Lcs_Length(string x, string y, int b[][Y+1],int xlen,int ylen) { int i = 0; int j = 0; int c[X+1][Y+1]; for (i = 0; i<=xlen; i++) { c[i][0]=0; } for (i = 0; i <= ylen; i++ ) { c[0][i]=0; } for (i = 1; i <= xlen; i++) { for (j = 1; j <= ylen; j++) { if (x[i - 1] == y[j - 1]) { c[i][j] = c[i-1][j-1]+1; b[i][j] = 1; } else if (c[i-1][j] > c[i][j-1]) { c[i][j] = c[i-1][j]; b[i][j] = 2; } else if(c[i-1][j] < c[i][j-1]) { c[i][j] = c[i][j-1]; b[i][j] = 3; } else { c[i][j] = c[i][j-1]; //或者c[i][j]=c[i-1][j]; b[i][j] = 4; } } } cout << "计算最优值效果图如下所示:" << endl; for(i = 1; i <= xlen; i++) { for(j = 1; j < ylen; j++) { cout << c[i][j] << " "; } cout << endl; } return c[xlen][ylen]; } /*功能:计算最长公共子序列 *参数: * xlen:字符串x的长度 * ylen:字符串y的长度 * x :字符串x * b:标志数组 * current_len:当前长度 * lcs_max_len:最长公共子序列长度 * */ void Display_Lcs(int i, int j, string x, int b[][Y+1],int current_len,int lcs_max_len) { if (i ==0 || j==0) { for(int s=0; s < lcs_max_len; s++) { cout << result[s]; } cout<<endl; count++; return; } if(b[i][j]== 1) { current_len--; result[current_len]=x[i- 1]; Display_Lcs(i-1, j-1, x, b,current_len,lcs_max_len); } else { if(b[i][j] == 2) { Display_Lcs(i-1, j, x, b,current_len,lcs_max_len); } else { if(b[i][j]==3) { Display_Lcs(i, j-1, x, b,current_len,lcs_max_len); } else { Display_Lcs(i,j-1,x,b,current_len,lcs_max_len); Display_Lcs(i-1,j,x,b,current_len,lcs_max_len); } } } } int main(int argc, char* argv[]) { string x = "ABCBDAB"; string y = "BDCABA"; int xlen = x.length(); int ylen = y.length(); int b[X + 1][Y + 1]; int lcs_max_len = Lcs_Length( x, y, b, xlen,ylen ); cout << lcs_max_len << endl; Display_Lcs( xlen, ylen, x, b, lcs_max_len, lcs_max_len ); cout << "共有:" << count << "种"; return 0; }
所有情况代码:
//动态规划之最长公共子序列
//输出:最长子序列长度和子序列
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
//字符串处理 int LCS_LENGTH(const char* str1, const char* str2,int c[][100],int b[][100]) { if ((str1 == NULL) || (str2 == NULL)) return 0; int m = strlen(str1); int n = strlen(str2); for (int i = 1; i < m; i++) c[i][0] = 0; for (int i = 1; i < n; i++) c[0][i] = 0; c[0][0] = 0; for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (str1[i-1] == str2[j-1]) { c[i][j] = c[i - 1][j - 1] + 1; b[i][j] = 1; } else if (c[i - 1][j] >= c[i][j - 1]) { c[i][j] = c[i - 1][j]; b[i][j] = 2; } else { c[i][j] = c[i][j - 1]; b[i][j] = 3; } cout << "计算最优值效果图如下所示:" << endl; for (int i = 0; i <= m; i++) { for (int j = 0; j <= n; j++) { cout << c[i][j] << " "; } cout << endl; } return c[m] ; } void print_LCS(int b[][100], const char* str, int i, int j) { if ((i == 0) || (j == 0)) return; if (b[i][j] == 1) { print_LCS(b, str, i - 1, j - 1); cout << str[i - 1]; } else if (b[i][j] == 2) print_LCS(b, str, i - 1, j); else print_LCS(b, str, i, j - 1); }
//整数处理
int LCS_LENGTH_INT(vector<int>& vec1, vector<int>& vec2, int c[][100], int b[][100])
{
int m = vec1.size();
int n = vec2.size();
if ((m == 0) || (n == 0)) return 0;
for (int i = 1; i < m; i++)
c[i][0] = 0;
for (int i = 1; i < n; i++)
c[0][i] = 0;
c[0][0] = 0;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
if (vec1[i - 1] == vec2[j - 1])
{
c[i][j] = c[i - 1][j - 1] + 1;
b[i][j] = 1;
}
else if (c[i - 1][j] >= c[i][j - 1])
{
c[i][j] = c[i - 1][j];
b[i][j] = 2;
}
else
{
c[i][j] = c[i][j - 1];
b[i][j] = 3;
}
cout << "计算最优值效果图如下所示:" << endl;
for (int i = 0; i <= m; i++)
{
for (int j = 0; j <= n; j++)
{
cout << c[i][j] << " ";
}
cout << endl;
}
return c[m]
;
}
void print_LCS_INT(int b[][100], vector<int>& vec , int i, int j)
{
if ((i == 0) || (j == 0)) return;
if (b[i][j] == 1)
{
print_LCS_INT(b, vec, i - 1, j - 1);
cout << vec[i - 1];
}
else if (b[i][j] == 2)
print_LCS_INT(b, vec, i - 1, j);
else
print_LCS_INT(b, vec, i, j - 1);
}
int main()
{
string str1,str2;
int c[100][100] = {0};
int b[100][100] = {0};
//字符串处理
cin >> str1 >> str2;
cout << LCS_LENGTH(str1.c_str(), str2.c_str(), c, b) << endl;
print_LCS(b, str1.c_str(), str1.size(), str2.size());
cout << endl;
//整数处理
vector<int> vec1 = { 1, 3, 5, 2, 4, 6, 7, 8 };
vector<int> vec2 = { 1, 2, 3, 4, 5, 6, 7, 8 };
cout << LCS_LENGTH_INT(vec1, vec2, c, b) << endl;
print_LCS_INT(b, vec1, vec1.size(), vec2.size());
cout << endl;
system("pause");
return 0;
}
相关文章推荐
- C++动态规划之最长公子序列实例
- C++动态规划之背包问题解决方法
- C#使用动态规划解决0-1背包问题实例分析
- N天测试网络视频会议系统
- 动态规划
- C++ 动态规划
- 最长公共字串
- 动态规划解决背包问题的核心思路
- DP(动态规划) 解游轮费用问题
- 动态规划的用法——01背包问题
- 动态规划的用法——01背包问题
- 《收集苹果》 动态规划入门
- 《DNA比对》蓝桥杯复赛试题
- 《背包问题》 动态规划
- 自顶向下动态规划解决最长公共子序列(LCS)问题
- 01背包问题
- 初学ACM - 半数集(Half Set)问题 NOJ 1010 / FOJ 1207
- 关于爬楼梯的动态规划算法
- 91. Decode Ways 动态规划-极客学院
- 动态规划系列【2】最长递增子序列LIS