实验内容:
本实验要求基于算法设计与分析的一般过程(即待求解问题的描述、算法设计、算法 描述、算法正确性证明、算法分析、算法实现与测试),在针对最长公共子序列问题求解的 实践中理解动态规划 (Dynamic Programming, DP) 方法的思想、求解策略及步骤。
实验目的:
◆ 理解动态规划方法的核心思想以及动态规划方法的求解过程;
◆ 从算法分析与设计的角度,对最长公共子序列问题的基于 DP 法求解有更进一步的 理解。
实验步骤:
步骤 1:理解问题,给出问题的描述。
字符序列的子序列是指从给定字符序列中随意地去掉若干个字符后所形成的字符序列。
令给定的字符序列 X=“x0,x1,…,xm-1”,序列 Y=“y0,y1,…,yk-1”是X的子序列,存在 X 的一个严格递增下标序列,使得对所有的 j=0,1,…,k-1,有 xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是 X 的一个子序列。
考虑最长公共子序列问题分解成子问题,设 A=“a0,a1,…,am-1”,B=“b0, b1,…,bm-1”,并 Z=“z0,z1,…,zk-1”为它们的最长公共子序列。有以下性质:
(1) 如果 am-1=bn-1,则 zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…, am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果 am-1!=bn-1,则若 zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…, am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果 am-1!=bn-1,则若 zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…, am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。 在找 A 和 B 的公共子序列时,如有 am-1=bn-1,则进一步解决一个子问题,找“a0, a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果 am-1!=bn-1,则 要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共 子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再 取两者中较长者作为 A 和 B 的最长公共子序列。
步骤 2:算法设计,包括算法策略与数据结构的选择;
第 1:确定合适的数据结构。采用二维数组 c 来存 放各个子问题的最优解,二维数 组 b 记录各子问题最 优值的来源。
第 2:初始化。令 c[i][0]=0, c[0][j]=0,其中, 0≤i≤m,0≤j ≤n。
第 3:循环阶段。根据递归关系式,确定 Xi 和 Yj 的 LCS 的长度,0≤i≤m。对于每个 i 循环,0≤j ≤n。
第 4:根据二维数组 b 记录的信息以自底向上的方 式来构造该 LCS 问题的最优解
步骤 3:算法正确性证明。需要这个环节,在理解的基础上对算法的正确性给予证明;
用 c[i][j]记录序列 Xi 和 Yj 的 LCS 的长度。 其中, Xi ={x1,x2,…,xi }和 Yj ={y1,y2,…,yj }分 别为序列 X 和 Y 的第 i 个前缀和第 j 个前缀。 当 i=0 或 j=0 时 ,空序列是 Xi 和 Yj 的 LCS 。此 时 C[i][j]=0, 其他情况下,由最优子结构性质可建立递归 关系
步骤 4:算法复杂性分析,包括时间复杂性和空间复杂性;
由于只需要填一个 m 行 n 列的二维数组,其中 m 代表第一个字符串长度,n 代表第二个字符串长度,所以时间复杂度为 O(m*n),空间复杂性因为为二维数组,故其空间复杂度为 O(mn)
步骤 5:算法实现与测试。这里附上代码
#include <iostream>
#include <string.h>
#define MaxLen 100
using namespace std;
int LcsLength(char *x,char *y,int b[][MaxLen])
{
int m,n;
int c[MaxLen][MaxLen];
m=strlen(x);
n=strlen(y);
for(int i=1;i<=m;i++)
c[i][0]=0;
for(int j=1;j<=n;j++)
c[0][j]=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;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
{
c[i][j]=c[i][j-1];
b[i][j]=3;
}
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
cout<<c[i][j]<<" ";
}
cout<<endl;
}
return c[m][n];
}
void lcs(int i,int j,char *x,int b[][MaxLen])
{
if(i==0||j==0)
return ;
if(b[i][j]==1)
{
lcs(i-1,j-1,x,b);
cout<<x[i-1];
}
else if(b[i][j]==2)
{
lcs(i-1,j,x,b);
}
else
{
lcs(i,j-1,x,b);
}
}
int main()
{
int b[MaxLen][MaxLen];
char x[MaxLen];
char y[MaxLen];
int m,n,count;
cout<<"序列 A:";
gets(x);
cout<<"序列 B:";
gets(y);
m=strlen(x);
n=strlen(y);
count=LcsLength(x,y,b);
cout<<"最长公共子序列为:";
lcs(m,n,x,b);
cout<<endl;
cout<<"最长公共子序列长度="<<count<<endl;
system("pause");
return 0;
}
实验总结:
用动态规划求解最优化问题的第一步就是刻画最优解的结构,使用动态规划算法时,用子 问题的最优解来构造原问题的最优解。动态规划法求解的问题特征:
1.最优子结构性质
2. 子问题重叠性质
3.自底向上求解
4.求解过程中保存子问题的解。
动态规划法的思想:
1.将 待求解问题分解成若干子问题
2.先求解各个子问题
3.然后从这些子问题的解得到原问题的解。
动态规划的主要设计步骤
① 找出最优解的性质,并刻画其结构特征;
② 递归地定义最 优值;
③ 以自底向上的方式计算出最优值;
④ 根据计算最优值时得到的信息,构造最优解