三、动态规划
- 理解动态规划算法的概念;
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题
如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。
- 掌握动态规划算法的基本要素;
1)最优子结构性质
2)重复子问题性质
- 掌握设计动态规划算法的步骤。
1) 找出最优解的性质,并刻划其结构特征;
2) 递归地定义最优值;
3) 以自底向上的方式计算出最优值;
4) 根据计算最优值时得到的信息,构造最优解
3.1 矩阵连乘问题
问题描述:将矩阵连乘积 A i A i + 1 . . . A j A_iA_{i+1}...A_j AiAi+1...Aj 简记为A[i:j] ,这里i≤j 。计算A[i:j]。
3.2 最长公共子序列
问题描述:给定2个序列X={ x 1 , x 2 , … , x m x_1,x_2,…,x_m x1,x2,…,xm}和 Y={ y 1 , y 2 , … , y n y_1,y_2,…,y_n y1,y2,…,yn},找出X和Y的最长公共子序列。
LCSLength()函数用于计算出最优值
void LCSLength(int m, int n, char *x, char *y, int **c, int **b){
int i, j;
for(i = 1; i <= m; i++)
c[i][0] = 0;
for(i = 1; i <= n; i++)
c[0][i] = 0;
for(i = 1; i <= m; i++){
for(j = 1; j <= n; j++)
if (x[i] == y[j]){
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;
}
}
}
LCS()函数用于输出最长公共子序列
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);
}
3.3 流水作业调度
要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。
FlowShop()函数用于将流水作业的序号进行最优化排序并返回所用时间。
class Jobtype{
public:
int key;
int index;
bool job;
};
bool cmp(Jobtype a,Jobtype b){
///升序排序
return a.key<b.key;
}
int FlowShop(int n,int *a,int *b,int *c){
Jobtype *d=new Jobtype[n];
for(int i=0;i<n;i++){
d[i].index=i;
d[i].key=a[i]>b[i]?b[i]:a[i];
d[i].job=a[i]<b[i]?true:false;///返回true和false
}
//sort(d,n);//对数组d按关键字升序进行排序
sort(d,d+n,cmp);//按照c[]中作业时间增序排序
int j=0,k=n-1;
for(int i=0;i<n;i++){
if(d[i].job==true){ ///先把m1的装入最前面,以便于开始执行工作
c[j++]=d[i].index;
}
else{
c[k--]=d[i].index;///因为是升序,所以m2执行时,应该是先执行大的
}
}
///上面是对c【i】的一个执行工作的序号的排序
j=a[c[0]];///m1执行的工作时间
k=j+b[c[0]];///m2执行的工作时间
for(int i=1;i<n;i++){
j+=a[c[i]];///m1开始执行第二个工作的时间
k=j>k?(j+b[c[i]]):(k+b[c[i]]);
}
delete d;
return k;
}
3.4 0-1背包问题
问 题 的 形 式 描 述 是 : 给 定 c > 0 , w i > 0 , v i > 0 , 1 ≤ i ≤ n , 求 n 元 0 − 1 向 量 ( x 1 , x 2 , … , x n ) , 使 得 : 问题的形式描述是:给定c>0,w_i>0,v_i>0,1≤ i ≤n,求n元0-1向量(x_1, x_2, …, x_n),使得: 问题的形式描述是:给定c>0,wi>0,vi>0,1≤i≤n,求n元0−1向量(x1,x2,…,xn),使得:
∑ i = 1 n w i x i \displaystyle\sum_{i=1}^{n} w_ix_i i=1∑nwixi且 ∑ i = 1 n v i x i \displaystyle\sum_{i=1}^{n} v_ix_i i=1∑nvixi达到最大。
Knapsack()函数用于计算最大容量.
void Knapsack(int v[],int w[],int c,int n,int m[][10])
{
int jMax = min(w[n]-1,c);//背包剩余容量上限 范围[0~w[n]-1]
for(int j=0; j<=jMax;j++)
{
m[n][j]=0;
}
for(int j=w[n]; j<=c; j++)//限制范围[w[n]~c]
{
m[n][j] = v[n];
}
for(int i=n-1; i>1; i--)
{
jMax = min(w[i]-1,c);
for(int j=0; j<=jMax; j++)//背包不同剩余容量j<=jMax<c
{
m[i][j] = m[i+1][j];//没产生任何效益
}
for(int j=w[i]; j<=c; j++) //背包不同剩余容量j-wi >c
{
m[i][j] = max(m[i+1][j],m[i+1][j-w[i]]+v[i]);//效益值增长vi
}
}
m[1][c] = m[2][c];
if(c>=w[1])
{
m[1][c] = max(m[1][c],m[2][c-w[1]]+v[1]);
}
}
Traceback()函数用于计算是否装入背包
//x[]数组存储对应物品0-1向量,0不装入背包,1表示装入背包
void Traceback(int m[][10],int w[],int c,int n,int x[])
{
for(int i=1; i<n; i++)
{
if(m[i][c] == m[i+1][c])
{
x[i]=0;
}
else
{
x[i]=1;
c-=w[i];
}
}
x[n]=(m[n][c])?1:0;
}