ITA 15 动态规划

来源:互联网 发布:linux mac地址 编辑:程序博客网 时间:2024/05/29 17:19

动态规划和分治法的区别,

首先,分治法:将问题划分成一些独立的子问题,递归的求解各子问题,然后合并子问题的解而得到原问题的解。

动态规划:适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题,鉴于会重复的求解各子问题,DP对每个问题只求解一遍,将其保存在一张表中,从而避免重复计算。

DP算法的设计可以分为四个步骤:

①.描述最优解的结构。
②.递归定义最优解的值。
③.按自底而上的方式计算最优解的值。
④.由计算出的结果创造一个最优解。

例1 装配线问题

#include "stdafx.h"#include<iostream>using namespace std;#include<assert.h>#include<vector>#include<queue>#include<map>#include<cmath>#include<string>#include<stack>//动态规划,工厂转配线//e[i]是进入装配线i需要的时间//x[i]是离开装配线i需要的时间//a[1][j]表示在装配站S[1][j]所需时间//t[1][j]表示底盘从S[1][j]移动到S[2][j+1]所需时间,同理t[2][j]//f[i][j]分别表示在第i装配线上第j个装配站的最优解//ln[i][j]记录第i条装配线上,最优解时第j个装配站的前一个装配线是什么// 最优解是,f代表最小花费时间,ln表示最后出来时是从装配线1还是装配线2const int n = 7;//6站,0不使用int fastestWay(int l[][n], int f[][n], int a[][n],int t[][n-1],int *e,int *x, int n ){int i = 0, j = 0, ff = 0;//初始化f[1][1] = e[1] + a[1][1];f[2][1] = e[2] + a[2][1];//站点j依次计算,f[1][j] = min{f[1][j-1]+a[1][j], f[2][j-1]+t[2][j-1]+a[1][j]},//同理,f[2][j] = min{f[2][j-1]+a[2][j], f[1][j-1]+t[1][j-1]+a[2][j]}for(j = 2; j < n; ++ j){if(f[1][j-1]+a[1][j] >= f[2][j-1]+t[2][j-1]+a[1][j]){f[1][j] = f[2][j-1]+t[2][j-1]+a[1][j];l[1][j] = 2;}else{f[1][j] = f[1][j-1]+a[1][j];l[1][j] = 1;}if(f[2][j-1]+a[2][j] >= f[1][j-1]+t[1][j-1]+a[2][j]){f[2][j] = f[1][j-1]+t[1][j-1]+a[2][j];l[2][j] = 1;}else{f[2][j] = f[2][j-1]+a[2][j];l[2][j] = 2;}}if(f[1][n-1]+x[1] < f[2][n-1]+x[2]){ff = f[1][n-1]+x[1];l[1][1] = 1;//利用l[1][1]的空间存放最后经过的装配线}else{ff = f[2][n-1]+x[2];l[1][1] =2;}return ff;}int main(){int i, j, k;//i装配线个数,j装配线上的站数,k返回值变量,n是站点数+1(1-7)int a[3][7],t[3][6];int e[3]={0,2,4};int x[3]={0,3,2};int l[3][7], f[3][7];int b[2][6]={{7,9,3,4,8,4}, {8,5,6,4,5,7}};int c[2][5]={{2,3,1,3,4},{2,1,2,2,1}};int cc[n+1];//用于将l[i][j]中的结果正向输出for(i=0; i < 2 ; ++ i)for(j=0; j < 6; ++ j)a[i+1][j+1] = b[i][j];for(i=0; i < 2; ++ i)for(j = 0; j < 5; ++ j)t[i+1][j+1] = c[i][j];k = fastestWay(l,f,a,t,e,x,n);cout<<"最少的时间是:"<<k<<endl;cc[n] = l[1][1];for(i=6; i >=2; -- i)cc[i] = l[cc[i+1]][i];//l[i][j]记录第i条装配线上,最优解时第j个装配站的前一个装配线是什么//cc[i]装配站i的前一站所在的装配线,即i-1所在装配线for(i=2; i <=n; ++ i)cout<<"汽车装配经过的装配线和装配站情况如下:"<<cc[i]<<","<<i-1<<endl;}

例2 矩阵乘链

//动态规划,矩阵乘链//设m[i][j]为计算矩阵Ai...j所需的标量乘法运算次数的最小值;//对整个问题,计算A1...n的最小代价就是m[1][n]。//    m[i][j]=0。i=j时//   m[i][j]=min{ m[i][k]+m[k+1][j]+pi-1pkpj} 在i!=j时。因为Ai是pi-1*pi.//定义s[i][j]为这样的一个k值:在该处分裂乘积AiAi+1...Aj后可得一个最优加全部括号。//亦即s[i][j]等于使得m[i][j]取最优解的k值。const int n = 7;int matrixChainOrder(int *p, int s[][n] ){int max = 20000;int i = 0, j = 0, l = 0, k = 0, q = 0, c = n-1;//7个p,6个矩阵int m[n][n];//初始化for(i = 0; i < n ;++ i)m[i][i] = 0;for(l=2; l <= c; ++ l) //确定步长,即i与j之间的距离,l=2时表示j-i等于1{for(i=1; i <= c-l+1; ++ i)//确定起点{j=i+l-1;//确定终止点m[i][j] = max;for(k=i; k < j; ++ k)//确定k值,起点与终点间最好的k值{q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];if(q < m[i][j]){m[i][j] = q;s[i][j] = k;}}}}return m[1][n-1];}void printOptimalParens(int s[][n],int i,int j){     if(i==j)         printf("A%d",i);     else      {         printf("(");         printOptimalParens(s,i,s[i][j]);         printOptimalParens(s,s[i][j]+1,j);         printf(")");     }}int main(){int k;//k返回值,矩阵相乘所需的标量乘法的最小值int s[n][n];int p[n]={30,35,15,5,10,20,25};k=matrixChainOrder(p,s);cout<<"矩阵相乘所需的标量乘法的最小值为:"<<k<<endl;cout<<"最终的最优全括号形式为:"<<endl;printOptimalParens(s,1,6);}

例 3 最长公共子序列

#include "stdafx.h"#include<iostream>using namespace std;#include<assert.h>#include<vector>#include<queue>#include<map>#include<cmath>#include<string>#include<stack>//动态规划,最长公共子序列LCS//给定两个序列x和y,称z是x和y的公共子序列,如果z既是x的子序列,又是y的子序列;//最长的公共子序列称作最长公共子序列LCS(longest common subsequence)//(1)LCS的最优子结构////  设zk是xm和yn的一个LCS,则,如果x和y的最后一个元素相同,则z中去掉最后一个元素之后zk-1仍为xm-1和yn-1的LCS////  如果xm!=yn,若zk!=xm,则z是xm-1和y的一个LCS,若zk!=yn,则z是xm和yn-1的LCS。////(2)一个递归解////  设c[i][j]为序列xi和yj的一个LCS的长度,则有:////    c[i][j]=0                                           i=0或j=0////    c[i][j]=c[i-1][j-1]+1                          xi=yj且i,j>0//        c[i][j]=max(c[i][j-1] , c[i-1][j])           xi!=yj且i,j>0//(3)计算LCS的长度void LCS_Length(char* x, char* y, int** c, int** b,int m, int n){int i = 0, j = 0;cout<<m<<","<<n<<endl;//初始化for(i = 1; i <= m; ++ i){c[i][0] = 0;//0列//cout<<c[i][0]<<endl;}for(j = 0; j <=n; ++ j){c[0][j] = 0;//0行//cout<<c[0][j]<<endl;}for(i = 1; i <=m; ++ i)for(j = 1; j <= n; ++j){if(x[i-1] == y[j-1])//因为x,y都是从0下标开始计算的{c[i][j] = c[i-1][j-1]+1;b[i][j] = 0;/*cout<<c[i][j]<<endl;*/}else if(c[i-1][j] >= c[i][j-1])//b[i][j]记录最大值来自哪里,左边2,上面1,对角0{c[i][j] = c[i-1][j];b[i][j] = 1;//cout<<c[i][j]<<endl;}else {c[i][j] = c[i][j-1];b[i][j] = 2;//cout<<c[i][j]<<endl;}}}void Print_LCS(int** b, char* x, int i, int j){if(i == 0 || j == 0)return;if(b[i][j] == 0){Print_LCS(b,x,i-1,j-1);cout<<x[i-1];}else if(b[i][j] == 1)Print_LCS(b,x,i-1,j);else Print_LCS(b,x,i,j-1);}int main(){cout<<"LCS问题,请输入X,Y字符串的长度:"<<endl;int m = 0, n = 0, i = 0;int** c = new int*[m+1];//c[0...m,0...n]int** b = new int*[m+1];//b[1...m,1...n]索引的是从1-m,1-n但是保证可以索引1-m,1-n必须有0-m,0-n的空间cin>>m>>n;cout<<m<<","<<n<<endl;char* x = new char[m];char* y = new char[n];for(i = 0; i < m; ++ i)cin>>x[i];//for(i = 0; i < m; ++ i)//cout<<x[i];//cout<<endl;for(i = 0; i < n; ++ i)cin>>y[i];//for(i = 0; i < n; ++ i)//cout<<y[i];//cout<<endl;for(i = 0; i <= m; ++ i){c[i] = new int[n+1];b[i] = new int[n+1];}LCS_Length(x,y,c,b,m,n);Print_LCS(b,x,m,n);}

坑爹的地方1,LCS中第二个循环,(j=0;j<=n;++i)写错了不报错,一直循环赋值,找了半天才找到错误

坑爹的地方2,b[1...m,1...n]记录长度来源的地方,为了使用下标1...m,1...n,需要有0...m,0...n的空间不然访问不存在的空间。

课后习题15.4-2,不使用b的情况,根据c的值判断应该怎么输出,但是还是需要O(M*N)的空间

void Print_LCS(int** c, char* x, int i, int j){if(i == 0 || j == 0)return;if(c[i][j] == (c[i-1][j-1]+1)){Print_LCS(c,x,i-1,j-1);cout<<x[i-1];}else if(c[i][j] == c[i-1][j])Print_LCS(c,x,i-1,j);else Print_LCS(c,x,i,j-1);}

15.4-4 如何将空间使用减少到2*min(m,n),首先使用string作为输入,简单方便,其次,细节问题就是因为m,n大小事先不知道,需要判断,使得x中总是保存长的string,这样在循环中保证数组是min(m,n)+O(1)空间,因为例子是7,6数组看不出差别。

两行,一行是pre,一行是cur只保存这两个就够了
void LCS_Length(string x, string y){int i = 0, j = 0, k=0;int m = x.length();int n = y.length();int t = m > n ? n:m;if(m<=n){string temp = x;x = y;y = temp;int count = m;m = n;n = count;}int *pre = new int[t+1];int *cur = new int[t+1];//初始化for(i = 0; i <= t; ++ i){pre[i] = 0;}<span style="white-space:pre"></span>cur[0] = 0;for(i = 1; i <=m; ++ i){for(j = 1; j <= n; ++j){if(x[i-1] == y[j-1])//因为x,y都是从0下标开始计算的{cur[j] = pre[j-1]+1; }else if(cur[j-1] <= pre[j] ){cur[j] = pre[j];}else {cur[j] = cur[j-1];}}for(k=1; k <= t; ++ k){pre[k] = cur[k];cur[k] =0;}}cout<<pre[t]<<endl;}int main(){cout<<"LCS问题,请输入X,Y字符串:"<<endl;string x, y;cin>>x;cin>>y;LCS_Length(x,y);}

改进2,使得空间使用为min(m,n)+O(1),相当的巧妙,看了好久还画图才明白,



void LCS_Length(string x, string y){int i = 0, j = 0, k=0;int m = x.length();int n = y.length();int t = m > n ? n:m;if(m<=n){string temp = x;x = y;y = temp;int count = m;m = n;n = count;}int *a = new int[t];//初始化for(i = 0; i <t; ++ i){a[i] = 0;}int cij = 0,ci1 = 0;for(i = 1; i <=m; ++ i){if(x[i-1] == y[0])ci1 = 1;else if(a[1]>=0)ci1 = a[1];a[0] = ci1;cout<<ci1;for(j = 2; j <= n; ++j){if(x[i-1] == y[j-1])//因为x,y都是从0下标开始计算的{cij = a[j-1]+1; cout<<cij;}else if(a[0] <= a[j] ){cij = a[j];cout<<cij;}else {cij = a[0];cout<<cij;}a[j-1] = a[0];a[0] = cij;}cout<<endl;}cout<<a[t-1]<<endl;}





0 0
原创粉丝点击