一、算法要求
若给定序列 X={x1,x2,…,xm},则另一序列 Z={z1,z2,…,zk},是 X 的子序列是指存在一 个严格递增下标序列{i1,i2,…,ik}使得对于所有 j=1,2,…,k 有:zj=xij。
例如,序列 Z={B,C, D,B}是序列 X={A,B,C,B,D,A,B}的子序列,相应的递增下标序列为{2,3,5,7}。
给定 2 个序列 X 和 Y,当另一序列 Z 既是 X 的子序列又是 Y 的子序列时,称 Z 是序列 X 和 Y 的公共子序列,例:给定 2 个序列 X={x1,x2,…,xm}和 Y={y1,y2,…,yn},找出 X 和 Y 的最长公共子序列。
1. 思路
构造最长公共子序列
由于每个数组单元的计算耗费O(1)时间,因此算法LCSLength耗时O(mn)。
由算法LCSLength计算得到的数组b可用于快速构造序列X = { x1,x2,…,xm }和Y = {y1, y2, …, yn}的最长公共子序列。
首先从b[m][n]开始,依其值在数组b中搜索。
当b[i][i] = 1时,表示X和Y的最长公共子序列是由Xi - 1和Yj - 1的最长公共子序列在尾部加上x1所得到的子序列。
当b[i][i] = 2时,表示X和Y 的最长公共子序列与Xi - 1和Y; 的最长公共子序列相同。
当b[i][i] = 3时,表示X和Y的最长公共子序列与X和Yj - 1的最长公共子序列相同。
2. 示例
二、完整代码
1. 主文件
main.cpp:
// Project2_1: 最长公共子序列问题
#include"Basic1.h"
int main() {
cout << "The current comparison sequence is: \n";
cout << "X: ";
for (int i = 1; i < row; i++) {
cout << setw(3) << X[i];
}
cout << "\nY: ";
for (int i = 1; i < column; i++) {
cout << setw(3) << Y[i];
}
cout << "\n\n";
LCSplay(flatArray, column, row, X, Y);
}
2. 头文件
Basic1.h:
#pragma once
#ifndef __BASIC1__
#define __BASIC1__
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
using namespace std;
string X = "1ABCBDAB",
Y = "0BDCABA";
int flatArray[100][100],
column = Y.length(),
row = X.length();
//LCS计数及展示
void LCSplay(int f[][100], int capacityPack, int r, string x, string y)
{
//防止数组越界,添加一下前导符
for (int i = 1; i < column; i++) {
for (int j = 1; j < row; j++) {
if (Y[i] == X[j])
flatArray[i][j] = flatArray[i - 1][j - 1] + 1;
else
flatArray[i][j] = max(flatArray[i - 1][j], flatArray[i][j - 1]);
}
}
cout << "The length of the largest common subsequence is: "
<< flatArray[column - 1][row - 1] << endl;
//反演得出最小公共子序列的值
cout << "The common subsequence is: ";
int colInver = column - 1, rowInver = row - 1;
while (colInver >= 1 && rowInver >= 1) {
if (Y[colInver] == X[rowInver]) {
cout << setw(3) << Y[colInver];
colInver -= 1;
rowInver -= 1;
}
else if (flatArray[colInver - 1][rowInver] >= flatArray[colInver][rowInver - 1]) {
colInver -= 1;
}
else
rowInver -= 1;
}
}
// b[i][j]:记录c[i][j]的值是由哪一个子问题的解得到的;
// c[m][n]:记录问题的最优值,即X和Y的最长公共子序列的长度。
// 解决指向性的问题
void LCSLength(int m, int numStuff, char* x, char* y, int** capacityPack, int** b) {
int i, j;
//表格初始化
for (i = 0; i <= m; i++)
capacityPack[i][0] = 0;
for (i = 0; i <= numStuff; i++)
capacityPack[0][i] = 0;
//判断数列规则(三向箭头的表示方法)
for (i = 1; i <= m; i++) {
for (j = 1; j <= numStuff; j++) {
if (x[i] == y[j]) {
capacityPack[i][j] = capacityPack[i - 1][j - 1] + 1;
b[i][j] = 1;
}
else if (capacityPack[i - 1][j] >= capacityPack[i][j - 1]) {
capacityPack[i][j] = capacityPack[i - 1][j];
b[i][j] = 2;
}
else {
capacityPack[i][j] = capacityPack[i][j - 1];
b[i][j] = 3;
}
}
}
}
void LCS(int i, int j, char* x, int** b) {
if (i == 0 || j == 0)
return;
if (b[i][j] == 1) {
LCS(i - 1, j - 1, x, b);
cout << x[i];
}
else if (b[i][j] == 2)
LCS(i - 1, j, x, b);
else
LCS(i, j - 1, x, b);
}
#endif
3. 效果展示
输入:
输出:
三、补充
LCS的最优子结构
设序列X={x1,x2,…,xm}和Y={y1,y2,…,yn}的最长公共子序列为Z={z1,z2,…,zk} ,则
(1)若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。
(2)若xm≠yn且zk≠xm,则Z是Xm-1和Y的最长公共子序列。
(3)若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公共子序列。
其中,Xm-1={x1,x2,…,xm-1};Yn-1={y1,y2,…,yn-1};Zk-1={z1,z2,…,zk-1}
在算法lcsLength和lcs中,可进一步将数组b省去。
事实上,数组元素c[i][j]的值仅由c[i-1][j-1],c[i-1][j]和c[i][j-1]这3个数组元素的值所确定。
对于给定的数组元素c[i][j],可以不借助于数组b, 而仅借助于c本身在时间内确定c[i][j]的值是由c[i-1][j-1],c[i-1][j]和c[i][j-1]中哪一个值所确定的。
如果只需要计算最长公共子序列的长度,则算法的空间需求可大大减少。
事实上,在计算c[i][j]时,只用到数组c的第i行和第i-1行。因此,用2行的数组空间就可以计算出最长公共子序列的长度。进一步的分析还可将空间需求减至O(min(m,n))。
文档供本人学习笔记使用,仅供参考。