是老师的作业,只是无情的完成作业机器罢了(
题目:
输入:由n个数组成的一个序列S:a1,a2,…,an,
由m个数组成的一个序列T: b1,b2,…bm.
输出:S和T的公共子序列X=c1c2,…,ck ,满足:
(1) c1<=c2 <= … <= ck ,
(2) |X|最大
解法:首先是错误解法!!!
哭了,写了半天的代码结果老师说是错误的,不甘心还是放上来以此为鉴,反正也是LCS和LIS的思路
错误解法是笨方法,就是先求取最长公共子序列(LCS, Longest Common String),再求取其最长递增子序列(LIS, Longest Increasing String),建议先了解LCS和LIS的各自的思路再看代码。
求取最长递增子序列(LIS):
int findLIS(int x[], int* d, int length_x){ // 求取并存储最长递增子序列
d[0] = x[0]; // d为存储的器具
int len = 1; // LIS的长度,初始为1
for(int i=1; i<length_x; i++){
if (len==1){ // LIS长度为1时单独判断
if (d[0]>x[i])
d[0] = x[i];
else{
d[1] = x[i];
len++;
}
}
else if (x[i]<d[len-1] && x[i]>=d[len-2]){ // 发现当前元素可替换LIS最后一个元素,有点像贪心算法
d[len-1] = x[i];
}
else if (x[i]>=d[len-1]){ // 比LIS最后一个元素大,纳入其中
d[len] = x[i];
len++;
}
}
return len; // 返回LIS的长度
}
求取最长公共子序列(LCS):
#define max_length 100 // 规定求取的数组的最大长度
void findLCS(int C_choice[][max_length],
int x[], int* LCS_store, int i, int j, int k){ // 存储最长公共子序列
if (i==0 || j==0){}
else if (C_choice[i][j] == 1){ // 第一种收录方式,从对角线获取
LCS_store[--k] = x[i-1]; // LCS_store是存储器具
findLCS(C_choice, x, LCS_store, i-1, j-1, k);
}
else if (C_choice[i][j] == 2){ // 分别是第二种、第三种收录方式
findLCS(C_choice, x, LCS_store, i, j-1, k);
}
else{
findLCS(C_choice, x, LCS_store, i-1, j, k);
}
}
int main(){
int C[max_length][max_length]; // 最长公共子序列求取
int C_choice[max_length][max_length]; // 存储LCS的收录方式
int LCS_store[max_length]; // 用于存储最长公共子序列
for (int i=0; i<length_x; i++){ // 初始化边界
C[i][0] = 0;
}
for (int j=0; j<length_y; j++){
C[0][j] = 0;
}
for (int i=1; i<=length_x; i++){
for (int j=1; j<=length_y; j++){
if(x[i-1]==y[j-1]){ // 发现相同元素,从对角线处继承LCS的length并记录收录方式
C[i][j] = C[i-1][j-1]+1;
C_choice[i][j] = 1; // 收录方式1
// cout << C[i][j] << ' ';
}
else if(C[i][j-1]>C[i-1][j]){ // 继承左边或者上边较大的LCS的length并记录收录方式
C[i][j] = C[i][j-1];
C_choice[i][j] = 2; // 收录方式2
// cout << C[i][j] << ' ';
}
else{
C[i][j] = C[i-1][j];
C_choice[i][j] = 3; // 收录方式3
// cout << C[i][j] << ' ';
}
}
// cout << '\n';
}
findLCS(C_choice, x, LCS_store,
length_x, length_y, C[length_x][length_y]); // 求取LCS的LIS,即LICS
}
总代码:
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <string>
using namespace std;
#define max_length 100 // 规定求取的数组的最大长度
void findLCS(int C_choice[][max_length],
int x[], int* LCS_store, int i, int j, int k){ // 存储最长公共子序列
if (i==0 || j==0){}
else if (C_choice[i][j] == 1){ // 第一种收录方式,从对角线获取
LCS_store[--k] = x[i-1]; // LCS_store是存储器具
findLCS(C_choice, x, LCS_store, i-1, j-1, k);
}
else if (C_choice[i][j] == 2){ // 分别是第二种、第三种收录方式
findLCS(C_choice, x, LCS_store, i, j-1, k);
}
else{
findLCS(C_choice, x, LCS_store, i-1, j, k);
}
}
int findLIS(int x[], int* d, int length_x){ // 求取并存储最长递增子序列
d[0] = x[0]; // d为存储的器具
int len = 1; // LIS的长度,初始为1
for(int i=1; i<length_x; i++){
if (len==1){ // LIS长度为1时单独判断
if (d[0]>x[i])
d[0] = x[i];
else{
d[1] = x[i];
len++;
}
}
else if (x[i]<d[len-1] && x[i]>=d[len-2]){ // 发现当前元素可替换LIS最后一个元素,有点像贪心算法
d[len-1] = x[i];
}
else if (x[i]>=d[len-1]){ // 比LIS最后一个元素大,纳入其中
d[len] = x[i];
len++;
}
}
return len; // 返回LIS的长度
}
int LICS(int x[], int y[], int* d,
int length_x, int length_y){ // 求LCS,进而求LICS,算法关键函数
int C[max_length][max_length]; // 最长公共子序列求取
int C_choice[max_length][max_length]; // 存储LCS的收录方式
int LCS_store[max_length]; // 用于存储最长公共子序列
for (int i=0; i<length_x; i++){ // 初始化边界
C[i][0] = 0;
}
for (int j=0; j<length_y; j++){
C[0][j] = 0;
}
for (int i=1; i<=length_x; i++){
for (int j=1; j<=length_y; j++){
if(x[i-1]==y[j-1]){ // 发现相同元素,从对角线处继承LCS的length并记录收录方式
C[i][j] = C[i-1][j-1]+1;
C_choice[i][j] = 1; // 收录方式1
// cout << C[i][j] << ' ';
}
else if(C[i][j-1]>C[i-1][j]){ // 继承左边或者上边较大的LCS的length并记录收录方式
C[i][j] = C[i][j-1];
C_choice[i][j] = 2; // 收录方式2
// cout << C[i][j] << ' ';
}
else{
C[i][j] = C[i-1][j];
C_choice[i][j] = 3; // 收录方式3
// cout << C[i][j] << ' ';
}
}
// cout << '\n';
}
findLCS(C_choice, x, LCS_store,
length_x, length_y, C[length_x][length_y]); // 求取LCS的LIS,即LICS
return findLIS(LCS_store, d, C[length_x][length_y]); // 返回最终LICS的长度,便于打印
// return C[length_x][length_y];
}
int main(){
int x[5] = {5, 2, 1, 3, 4};
int y[7] = {9, 5, 2, 5, 3, 4, 1};
int d[max_length];
int len = LICS(x, y, d, 5, 7);
cout << "LICS: ";
for(int i=0; i<len; i++){
cout << d[i] << " ";
}
cout << "\nLICS_length: " << len;
}
说这是错误解法是因为实际上LICS不一定包含在LCS中,可能会出现这种情况:
反例:数组1:6, 5, 4, 1, 2, 3, 10, 10, 10, 10
数组2:10, 10, 10, 10, 6, 5, 4, 1, 2, 3
这里最大公共子序列(6,5,4,1,2,3)长度是6,以此求得的最长递增子序列(1,2,3)长度只有3,与此同时还有个公共子序列(10,10,10,10)长度为4,它是完全递增的子序列,它才是真正的LICS,所以是错误的。
以及也不可以先求两个数组各自的LIS,再求两个LIS的LCS,反例如下:
反例:数组1:3, 3, 3, 3, 3, 3, 1, 1, 1
数组2:4, 4, 4, 4, 4, 4, 1, 1, 1
正确解法:
大概说一下设置的max的思路,就是说咱们是一行一行来比对的。
假如现在的情景是君王 a 们在逐一纳妾 b ,每个君王 a[i] 单独分析,LICS就是君王看到 妾b[k] 时君王的纳妾数量,这数量越多越好。
君王喜欢从矮到高来纳妾,一旦碰到和自己身高一样的则考虑纳为正妻,是的,这样娶的妻子会更多。但是一旦有了正妻,又要保证从矮到高的顺序,就要保证前面的妾都要矮于正妻,或者说要矮于自己。
在轮到君王 a[i] 这一行的时候,一个一个看 b[k],先把以前君王们排的结果拿来准没错,所以初始的时候 LICS 都是等于自己上一行正上方的 LICS 的长度。就是上一个君王看到 b[k] 时他们的纳妾数量。
但是也要考虑如果日后出现 b[k] = a[i] 这种要设置正妻的情况,所以君王决定要设置max,搞个特殊的小名单,这里面女子既满足从矮到高又都比自己矮,这样以后就能用这个名单和正妻一把子纳入了。所以当 b[k] 比自己还矮(b[k] < a[i])的时候,那君王就要去看看上一任君王看到这里的时候记录的纳妾数量LICS,并和自己的小名单max比较,要是居然比自己多(f[i-1][k] > max),那就把名单换成上一任君王的名单。
一个小问题,那为什么君王在遇到了和自己一样高的女子非要选择纳为正妻而不是沿用上一任君主的方案呢?(即为什么当 b[k] = a[i] 时,选择将 <b[k] = a[i]> 加入当前的LICS,即令 f[i][k] = max+1, 而不是令 f[i][k] = f[i-1][k])如果能想通这个问题那么也就差不多了。
以下是正确思路的实现代码:
#include<cstdio>
#include<cstring>
int f[1005][1005]; // 存储当前LICS的最大值
int a[1005], b[1005]; // 两个目标数组
int i, j; // 用于for循环的工具变量
int n, m, max; // 两个目标数组的长度以及每行所匹配的最大值
int result[1005]; // 用于存储最终的LICS结果
int main()
{
printf("请输入两个数组的长度:");
scanf("%d%d", &n, &m);
printf("数组1:");
for (i=1; i<=n; i++) scanf("%d", &a[i]);
printf("数组2:");
for (i=1; i<=m; i++) scanf("%d", &b[i]);
memset(f, 0, sizeof(f)); // 初始化f,将所有元素置为0
for(i=1; i<n; i++)
{
max = 0; // 每行进行计算的时候重置max
for(j=1; j<=m; j++)
{
f[i][j] = f[i-1][j]; // 不管怎样先等于上面f的准没错
if (a[i]>b[j] && max<f[i-1][j]){ // 假如当前匹配的b[j]比a[i]还小,就可以刷新max啦
max = f[i-1][j];
}
if (a[i] == b[j]){ // 出现等于的情况,可以将<a[i]=b[j]>收录进当前的LICS
f[i][j] = max+1; // 因为收录了新的元素,所以在max的基础上+1
if (max+1 == f[i-1][j]) max++; // 为了防止出现LICS中末尾元素等于倒数第二个元素的情况,这里需要增加max
}
}
}
max = 0;
int len = 0; // 记录最终的LICS的长度
for(j=1; j<=m; j++) // 将最后一行单独分析,因为最终的结果就出自最后一行
{
f[n][j] = f[n-1][j];
if (a[n]>b[j] && max<f[n-1][j]){
max = f[n-1][j];
result[len] = b[j]; // 前面分析与前n-1行相同,这里多出记录LICS
len++;
}
if (a[n] == b[j]){ // 是收录进LICS的关键操作,当然要记录进result中
f[n][j] = max+1;
if (max+1 == f[n-1][j]){
max++;
result[len] = b[j];
len++;
}
else if (max+1 > f[n-1][j]){
result[len] = b[j];
len++;
}
}
}
// for (i=0; i<=n; i++){ // 如果还是看不太懂可以取消这里的注释
// for (j=0; j<=m; j++){ // 看看f是如何计算的,这样会比较直观一点
// printf("%d ", f[i][j]);
// }
// printf("\n");
// }
printf("最长公共递增子序列为:");
for(i=0; i<len; i++){
printf("%d ", result[i]);
}
printf("\nLICS长度为:%d\n", len);
}