最长公共子序列
题目:给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。
示例:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3
解题思路:
比如我们要求text1=“acd”,text2="cde"的最长公共子序列。
第一步:
我们比较a和c的最长公共子序列,接着比较a和cd的最长公共子序列,再比较a和cde的最长公共子序列。
第二步:
我们比较ac和c的最长公共子序列,接着比较ac和cd的最长公共子序列,再比较ac和cde的最长公共子序列。
第三步:
我们比较acd和c的最长公共子序列,接着比较acd和cd的最长公共子序列,再比较acd和cde的最长公共子序列。
经过以上3步就可以得到最终答案为2。
思考:
我们比较a和c后,再去比较a和cd。其中比较a和cd中是不是包括了a和c的比较,这就是重叠子问题,而且字符串a和cd的最长子序列是不是包含了a和c的最长子序列,这称为最优子结构特性。满足以上两个要素就可以考虑使用动态规划来解决问题。
实现步骤:
1、初始化
以text1=“xzyzzyx”,text2=”zxyyzxz“为例子,我们要初始化以下8行8列的二维数组(第一行和第一列都是没有的,这里是为了方便填表才一起写在这里的)
text2 | z | x | y | y | z | x | z | |
---|---|---|---|---|---|---|---|---|
text1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
x | 0 | |||||||
z | 0 | |||||||
y | 0 | |||||||
z | 0 | |||||||
z | 0 | |||||||
y | 0 | |||||||
x | 0 |
这二维数组里0所代表的含义:
第2行和第2列的0都代表着test1还没有和test2比较时的最长公共子序列为0
2、填表
text2 | z | x | y | y | z | x | z | |
---|---|---|---|---|---|---|---|---|
text1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
x | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
z | 0 | 1 | 1 | 1 | 1 | 2 | 2 | 2 |
y | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 2 |
z | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 3 |
z | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 4 |
y | 0 | 1 | 1 | 2 | 3 | 3 | 3 | 4 |
x | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 |
填入数字代表的意思:
如:
第3行4列下的1就代表着x和zx的最长公共子序列为1
第6行7列下的3代表着xzyz和zxyyz的最长公共子序列为3
填入数字的规则及其来源:
来源:
第3行4列下的1,是因为x和z的最长公共子序列为0,text1没有和zx比较时最长公共子序列为0,max(0,0)=0,且x=x,所以0+1=1;即x和zx的最长公共子序列为1;
第8行9列下的4,是因为xzyzz和zxyyzxz的最长公共子序列为4,xzyzzy和zxyyzx的最长公共子序列为3,max(4,3)=4,且y!=z,所以xzyzzy和zxyyzxz的最长公共子序列为4;
规则:
当text1[i]=text2[j]时,c[i][j]=max(c[i][j-1],c[i-1][j])+1;
当text1[i]!=text2[j]时,c[i][j]=max(c[i][j-1],c[i-1][j]);
当i=0 or j=0,c[i][j]=0;
3、打印最长公共子序列
当text1[i]=text2[j]时,说明有公共子序列,所以进入c[i-1][j-1];
当text1[i]!=text2[j]时,若c[i-1][j]=c[i][j-1],说明有多条路径,但我这里只选择一条路径;若c[i-1][j]>c[i][j-1],进入c[i-1][j];若c[i-1][j]<c[i][j-1],进入c[i][j-1]
代码实现
#include<iostream>
using namespace std;
class LCS{
public:
LCS(int nx,int ny,char *x,char *y){
m=nx;
n=ny;
a=x;
b=y;
c=new int*[m];//初始化二维数组
for(int i=0;i<m;i++)
{
c[i]=new int[n];
}
}
int LCSLength();
void CLCS();
private:
void CLCS(int i,int j);
int **c,m,n;//c是二维数组 ,m是x数组的长度,n是y数组的长度
char *a,*b;//a是x,b是y
};
int LCS::LCSLength(){
for(int i=1;i<m;i++)
{
c[i][0]=0;
}
for(int i=0;i<n;i++)
{
c[0][i]=0;
}
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
if(a[i]==b[j])
{
c[i][j]=c[i-1][j-1]+1;
}
else if(c[i-1][j]>=c[i][j-1])
{
c[i][j]=c[i-1][j];
}
else{
c[i][j]=c[i][j-1];
}
}
}
return c[m-1][n-1];
}
void LCS::CLCS(){
CLCS(m-1,n-1);
}
void LCS::CLCS(int i,int j){
if(i==0||j==0) return;
if(a[i]==b[j])
{
CLCS(i-1,j-1);
cout<<b[j];
}
else
{
if(c[i-1][j]>=c[i][j-1])
{
CLCS(i-1,j);
}
else
{
CLCS(i,j-1);
}
}
}
int main(){
char x[8]={'0','a','b','c','b','d','a','b'};
char y[7]={'0','b','d','c','a','b','a'};
LCS l(8,7,x,y);//8和7是x,y长度,我这里是偷懒了
cout<<"最长子序列长度为:"<<l.LCSLength()<<endl;
l.CLCS();
}
0/1背包
0/1背包问题也是可以用动态规划来实现的,首先我们可以把包的大小可以进行划分,例如我们求最大承受重量为8的包可以装什么东西,使其装的东西价值最大(物体不可以进行切割),我们可以求最大承受重量分别为0、1、2、3、4、5、6、7和8的包的最大价值是什么,这样就使其具备了重叠子问题和最优子结构特性。实现原理和最长公共子序列几乎一样。
例如:
最大承受重量为8的背包,重量为(w1,w2,w3,w4)=(2,3,4,5),价值为(v1,v2,v3,v4)=(3,4,5,6),怎么装使其价值最大?
这里我们就不再多说了,直接进行初始化。
v | w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 2 | 0 | ||||||||
4 | 3 | 0 | ||||||||
5 | 4 | 0 | ||||||||
6 | 5 | 0 |
那一行0的意思是装重量为0和价值为0的物体放进背包的价值为0;
那一列0的意思是承受重量为0的背包装这些物品的价值都是0;
填表
v | w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 2 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
4 | 3 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
5 | 4 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 8 | 9 |
6 | 5 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
填表的规则、来源及其含义
来源及含义:
第5行第11列的9,代表着最大承受重量为8的背包,其最大价值为9。
因为8>=4,且8-4=4,看表得,没有装重量为4,价值为5,
最大承受重量为4的背包的最大价值为4(第4行第7列),
加上剩下的承受重量的最大价值即5+4=9。
9>7(不装该物品下的价值),所以其最大价值为9。
第6行第8列的7,代表着最大承受重量为5的背包,其最大价值为7。因为5>=5,但5-5=0,6+0(第5行第3列)<7(第5行第8列),所以其最大价值为7。
规则:
当w[i]>bag[j],c[i][j]=c[i-1][j]
当bag[j]>=w[i] && v[i]+c[i-1][bag[j]-w[i]]>c[i-1][j],c[i][j]=v[i]+c[i-1][bag[j]-w[i]]
输出搭配
规则
如果c[i][j]==c[i-1][j],表示该物品没有装或者其价值和装了它但没有装其他物品和价值一样,
这里我们选择不装它所以直接进入c[i-1][j];
如果c[i][j]!=c[i-1][j],表示该物品装入,所以直接进入c[i-1][j-w[i]]
代码实现
#include<iostream>
using namespace std;
class MV{
public:
MV(int n,int z,int *x,int *y)
{
num=n;
cap=z+1;
c=new int*[num];
for(int i=0;i<num;i++)
{
c[i]=new int[cap];
}
w=x;
v=y;
item=new int[num];
bag=new int[cap];
for(int i=0;i<cap;i++)//背包大小的赋值
{
bag[i]=i;
}
}
int MaxValue();
void Match();
void Print();
void Match(int i,int j);
private:
int num,cap;//num为物品数量,cap为背包最大承受重量
int *w,*v,*bag,*item;//bag为不同容量的背包
int **c;//二维数组
};
int MV::MaxValue(){
for(int i=1;i<num;i++)
{
c[i][0]=0;
}
for(int i=0;i<cap;i++)
{
c[0][i]=0;
}
for(int i=1;i<num;i++)
{
for(int j=1;j<cap;j++)
{ int value=v[i]+c[i-1][bag[j]-w[i]];
if(bag[j]>=w[i]&&value>c[i-1][j])
{
c[i][j]=value;
}
else{
c[i][j]=c[i-1][j];
}
}
}
for(int i=0;i<num;i++){
for(int j=0;j<cap;j++){
cout<<c[i][j]<<" ";
}
cout<<endl;
}
return c[num-1][cap-1];
}
void MV::Match()
{
Match(num-1,cap-1);
}
void MV::Match(int i,int j)
{ if(i>0)
{
if(c[i][j]==c[i-1][j])
{
item[i]=0;
Match(i-1,j);
}
else
{
item[i]=1;
Match(i-1,j-w[i]);
}
}
}
void MV::Print(){
for(int i=1;i<num;i++)
{
cout<<"重量为:"<<w[i]<<" "<<"价格为:"<<v[i]<<" "<<"的物品装"<<item[i]<<"件"<<endl;
}
}
int main(){
int w[5]={0,2,3,4,5};
int v[5]={0,3,4,5,6};
MV l(5,8,w,v);
cout<<"最大价值为:"<<l.MaxValue()<<endl;
l.Match();
l.Print();
}