关于用动态规划法求两个序列的最长公共子序列问题的相关知识见王晓东《计算机算法设计与分析》第三章。
注意,这里所指的最长公共子序列是可以不相邻的,与平常所说的最长公共子串(相邻的)不一样。
直接上代码:
LCS.h
#ifndef LCS_H #define LCS_H
class LCString { private: int m; // 序列X的长度 int n; // 序列Y的长度 char *x; // 序列X char *y; // 序列Y int **c; // 存储x(i)和y(j)的最长公共序列的长度 char **b; // 记录c[i][j]的值是由哪一个子问题的解得到的 bool Input(); void LCSLength(); // 计算最优值 void print(); // 打印 void LCS(int i,int j,char* x,char** b); // 构造最长公共子序列
public: LCString(); ~LCString(); bool Run(); // 运行接口函数 };
#endif |
LCS.cpp
#include "LCS.h" #include <iostream> #include <iomanip> using namespace std;
#define MAX 50
/// LCString::LCString() { m = n =0; c = new int*[MAX]; b = new char*[MAX]; for (int i=0;i<MAX;i++) { c[i] = new int[MAX]; b[i] = new char[MAX]; } x = new char[MAX]; y = new char[MAX]; }
LCString::~LCString() { for (int i=0;i<MAX;i++) { delete [] c[i]; delete [] b[i]; } delete [] c; delete [] b; delete [] x; delete [] y; }
bool LCString::Input() { cout << "请输入序列X:" << endl; cin >> x; m = strlen(x); cout << "请输入序列Y:" << endl; cin >> y; n = strlen(y);
if (x && y) return true; else return false; }
// void LCString::LCSLength() { int i,j; // ① C[i][j] = 0; (i=0,j=0) //for (i=1;i<=n;i++) // c[0][i] = 0; //for(i=1;i<=m;i++) // c[i][0] = 0; for(i=0;i<=m;i++) for(j=0;j<=n;j++) c[i][j] = 0;
for (i=1;i<=m;i++) { for (j=1;j<=n;j++) { // ②C[i][j]=C[i-1][j-1]+1; (i,j>0;x[i]=y[j]) if (x[i-1] == y[j-1]) { c[i][j] = c[i-1][j-1]+1; b[i][j] = '\\'; } // ③C[i][j]=max{C[i][j-1],C[i-1][j]}; (i,j>0;x[i]≠y[j]) else if (c[i-1][j] >= c[i][j-1]) { c[i][j] = c[i-1][j]; b[i][j] = '|'; } else { c[i][j] = c[i][j-1]; b[i][j] = '-'; } } } }
/ void LCString::print() { int i,j; cout << " "; cout << setw(2) << 'y'; for (j=0;j<=n;j++) cout << setw(2) << j; cout << endl << "x "; for (j=1;j<=n;j++) cout << setw(2) << y[j-1]; cout << endl << "0 "; for (j=1;j<=n;j++) cout << setw(2) << c[0][j]; cout << endl;
for (i=1;i<=m;i++) { cout <<" "; for (j=1;j<=n;j++) cout << setw(2) << b[i][j]; cout << endl;
cout <<i<< x[i-1] << " "; cout << setw(2) << 0;
for (j=1;j<=n;j++) cout << setw(2) << c[i][j]; cout << endl;
} cout << endl;
}
void LCString::LCS(int i, int j, char *x, char **b) { if(i==0 || j==0) return; if(b[i][j] == '\\') { LCS(i-1,j-1,x,b); cout << x[i-1]; } else if (b[i][j] == '|') LCS(i-1,j,x,b); else LCS(i,j-1,x,b); }
bool LCString::Run() { if (Input()) { LCSLength(); cout << endl << "计算结果为:\n"; print(); cout << "序列X和序列Y的最长公共子序列为:" << endl; LCS(m,n,x,b); cout << endl; return true; } else return false; }
int main() { LCString lcs; lcs.Run();
return 0; } |
运行结果(采用的是书上的例子):
该问题,由最优子结构性质建立递归关系如下:
(具体分析见王晓东《计算机算法设计与分析》)
c[i,j]用来存储X和Y的最长公共子序列的长度。
注意第一种情况,相应代码见上面代码黄色背景处。其中,注释掉的代码是王晓东《计算机算法与分析》一书中对应于第一种情况的代码。
for (i=1;i<=n;i++)c[0][i] = 0;这行代码完成的是对一个二维数组的列的赋值。所以这个二维数组在被使用之前一定要初始化,否则这里对列的赋值是不能够成功的。在LCS.h中,c的定义是char** c,可见c是一个二级指针,在构造函数中已经完成了对c的动态内存分配,但是没有初始化,所以不能对c的列进行赋值。
那么将c定义成char c[50][50]行不行呢?同样也是需要先对c[50][50]进行初始化后才能对列进行赋值。但是用CodeBlocks(GCC)却是可以的。为什么呢?举例如下:
int main()
{
int n;
cout<<n<<endl;
return 0;
}
显然,n是没有被初始化的,用vc++ 2008运行,会出错,但是用CodeBlocks(gcc)却可以打印出n的值。这是因为CodeBlocks(gcc)中,n虽然没有被初始化,但是编译器已经有个默认值赋给它了,所以能够打印出n的值。因此,为了避免不必要的麻烦与错误,变量在使用前最好初始化。
所以,可将
for (i=1;i<=n;i++)
c[0][i] = 0;
for (i=1;i<=m;i++)
c[i][0] = 0;
替换成
for (i=0;i<=m;i++)
for (j=0;j<=n;j++)
c[i][j] = 0;
因为二维数组c的值在整个程序中是≥0的,这样就既完成了对二维数组c的初始化,也完成了c[i,j]的第一种情况。
2009.12.5 Sat.