动态规划解最大回文子序列
子串和子序列有区别,子串是连续的,子序列不一定连续。
例如字符串 "bdsdksldjfkdls",它的一个子串为"dsdk",它的一个子序列为"bdklj"(1,2,5,7,9)。
一:刻画最优解的结构特征
假设序列为a,从a[i]到a[j]所包含的最大回文子序列的字符数为c[i,j],则所求的就是c[i,j]第一次取到最大值时的子序列。之所以是第一次取到最大值,是因为c[i,j+1]只可能大于等于c[i,j],若c[i,j]已经取到了最大值,那么a[i]和a[j]就是最大回文子序列的首尾,a[j+1]则不在该子序列中。
二:递归定义最优解的值
最优解的值是c[i,j],最优解是子序列,这二者是不一样的。
c[i,j] = c[i+1,j-1]+2 (a[i] = a[j])
c[i,j] = max(c[i+1,j],c[i,j-1]) (a[i] <> a[j])
三:采用自底向上法
这里的自底向上和以前的不一样。这里c[i,j]依赖c[i+1,j],说明要先求出c[i+1,j],所以i应该是递减的,即要先求出i=8,才能求出i=7。
四:构造最优解
构造需要最大子序列的首尾,最大子序列包含的字符数。
代码如下:
#include <iostream>
using namespace std;
#define max(x,y) ((x)>(y)?(x):(y))
char a[1024];
int c[100][100];
char b[1024];
int k = 0;
void cprint(int m, int n, int q);
int main()
{
memset(&a, 0, sizeof(a));
snprintf(a,sizeof(a),"character");
int alen = strlen(a);
memset(&c, 0, sizeof(c));
int q = -1;
int m = -1;
int n = -1;
c[alen][alen] = 1;
for(int i = alen-1; i>=0; i--)
{
for(int j = i; j<=alen; j++)
{
if(i == j)
{
c[i][j] = 1;
}
else if(j < i)
{
c[i][j] = 0;
}
else if(a[i] == a[j])
{
c[i][j] = c[i+1][j-1] + 2;
if(q < c[i][j])
{
q = c[i][j];
m = i;
n = j;
}
}
else
{
c[i][j] = max(c[i+1][j],c[i][j-1]);
}
}
}
cout<<q<<endl;
cprint(m,n,q);
return 0;
}
/*
打印最大回文子序列的函数采用递归的方式,如果a[i] = a[j],则打印a[i],如果不相等,则看是
c[i+1,j]大还是c[i,j-1]大,以此决定改舍弃a[i]还是a[j],然后递归调用打印函数,直到q = 0,说明前一半已经打印完了,再打印后一半,后一半是在打印前一半的时候已经储存了。
*/
void cprint(int m, int n, int q)
{
if(q == 1)
{
cout<<a[m];
q = 0;
}
if(q == 0)
{
for(int t = k-1; t>=0; t--)
cout<<b[t];
cout<<endl;
return;
}
if(a[m] == a[n])
{
cout<<a[m];
b[k] = a[m];
k++;
q = q-2;
cprint(m+1,n-1,q);
}
else if(c[m][n-1] > c[m+1][n])
{
cprint(m,n-1,q);
}
else
{
cprint(m+1,n,q);
}
}