动态规划的基本设计思想
适合用动态规划求解的问题的特征
基本性质:
- ①子问题重复
- ②子问题的解在下一阶段决策中,延续子问题多次使用
- 一个问题的最优解包含着它的子问题的最优解
动态规划算法设计的基本步骤
【例8-1】数塔
![](https://img-blog.csdnimg.cn/5724ba0c0edc4f3e8782acc5b391548b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_20,color_FFFFFF,t_70,g_se,x_16)
问题分析
计算模型:
(1)存储结构:
data[n][n]原始数据信息
r[n][n]存储每一阶段的路径的计算结果
path[n][n]存储下一步最优结点列坐标
(2)计算:阶段性最优
r[i][j] = max{ r[i+1][j] , r[i + 1][j + 1] } + data[i][j] i=n-2,...,1 ;j<=i
下一最优子节点的列坐标
path[i][j] = 0 if r[i+1][j] >= r[i+1][j+1] and 0<i<n,0<j<=i
path[i][j] = 1 if r[i+1][j] < r[i+1][j+1] and 0<i<n,0<j<=i
最优解:data[1][1] -> data[2][j] -> data[i+1][j]->....., i=j=1,j=j+path
最优值 r[1][1]
子集合最优的决策和记录路径
算法设计与描述 | 算法分析 |
输入:数塔data[n][n] | (1) |
输出:结点权值最大路径,及其值 | |
void datatower(int data[][N], int n) //先是最底层 也就是第一层的数,直接赋值给 //第二层到最后一层,也是阶段 //现阶段最优值计算 } |
代码:
#include<iostream>
using namespace std;
//数塔
//原结点
int const n=5+1;
int data[n][n]={{0},{0,8},{0,11,14},{0,10,5,8},{0,3,17,9,4},
{0,18,7,12,6,16}};
void show(int a[][n])
{
for(int i=1;i<n;i++)
{
for(int j=1;j<n-i+1;j++)
{
cout<<a[i][j]<<'\t';
}
cout<<endl;
}
}
void showd(int a[n][n])
{
for(int i=1;i<n;i++)
{
for(int j=1;j<i+1;j++)
{
cout<<a[i][j]<<'\t';
}
cout<<endl;
}
}
void showhang(int a[])
{
for(int i=0;i<n;i++)
cout<<a[i]<<'\t';
cout<<endl;
}
//
void datatower(int data[][n],int n)
{
int r[n][n] ={0};//存储每一阶段的路径计算结构
int path[n][n]={0};//存储下一步最优结点的列坐标
//显示最底层 直接赋值
for(int i=1;i<n;i++)
{
r[n-1][i]=data[n-1][i];
}
for(int i=n-2;i>0;i--)//逐层向上
{
for(int j=1;j<=i;j++)//当前层,也就是当前阶段的最优解计算
{
if(r[i+1][j]>r[i+1][j+1])
//当前层的下一层 类似于左右子节点进行比较
//选择较大的
{
r[i][j]=r[i+1][j]+data[i][j];
path[i][j]=0;//选择左节点
}
else
{
r[i][j]=r[i+1][j+1]+data[i][j];
path[i][j]=1;//选择左节点
}
}
}
//输出权值最大的路径及最大权值
cout<<"max :"<<r[1][1]<<endl;
int j=1;int i;
for(i=1;i<n-1;i++)
{
cout<<"["<<i<<","<<j<<"]"<<data[i][j]<<"-->";
j=j+path[i][j];
}
cout<<"["<<i<<","<<j<<"]"<<data[i][j];
}
int main()
{
showd(data);
datatower(data,n);
return 0;
}
/*
8
11 14
10 5 8
3 17 9 4
18 7 12 6 16
max :58
[1,1]8-->[2,1]11-->[3,1]10-->[4,2]17-->[5,3]12
*/
//优化 一维数组暂存数据
#include<iostream>
using namespace std;
//数塔
//原结点
int const n=5+1;
int data[n][n]={{0},{0,8},{0,11,14},{0,10,5,8},{0,3,17,9,4},
{0,18,7,12,6,16}};
void tower(int data[][n])
{
int i,j;
int r[n];//存每一行的数据
//每一次实际上都是比较刷新后的底层数据(加和之后)
//因此可以用一维数组进行存储
//这种比较是破坏性的 ,只需要最新的数据
int col[n][n]={0};
//这个不能用一维数组代替,因为
//最后是从顶向下进行找列坐标 ,然而比较是从底向上
//因此需要之前的数据
//差不多这个意思
//最底层的数据
for(i=0;i<n;i++)
{
r[i]=data[n-1][i];
}
//向上找
for(i=n-2;i>0;i--)
{
for(j=1;j<=i;j++)
{
//比较左右子结点
if(r[j]>r[j+1])
{
r[j]=r[j]+data[i][j];
//左结点大 记录左节点 用数据记录
col[i][j]=0;//第i层的选择
}
else//右结点大 记录右结点
{
r[j]=r[j+1]+data[i][j];
col[i][j]=1;//第i层的选择
}
}
}
//结束
cout<<"max:"<<r[1]<<endl;
j=1;
for(i=1;i<n;i++)
{
cout<<"["<<i<<","<<j<<"]"<<data[i][j];
if(i!=n-1)cout<<"-->";
j=j+col[i][j];
}
}
int main()
{
tower(data);
return 0;
}
投资分配问题
![](https://img-blog.csdnimg.cn/2320582c8b1e4c538ece56a7503ae912.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_12,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/4f22741bde6d4269a1d68799a9ed16b5.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/0b2d3cbec44943558186efde2cd54d61.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_20,color_FFFFFF,t_70,g_se,x_16)
(2)存储 设有m个项目,共投资n万元。最多m个阶段。
(3)求解最优方案
代码:
#include<iostream>
using namespace std;
void output(int a[])
{
for(int i=0;i<100;i++)
{
// if(a[i]!=0)
cout<<a[i]<<'\t';
}
}
void invest()
{
int a[100][100];//表示某阶段最大获利情况下分配给某个项目资金。
int g[100];//存储每一阶段的最优方案。运算完毕后,更新g[k]=t[k]。
int f[100];//存储某项目初始投资所获得利润。
//f[i]表示投资i万元给某项目所获得的利润(数组值)。
int t[100];//存储当前投资额的最大利润
int i,j,rest,n,m,k;
int gain[100];//存储整个投资的最优分配方案。
m=3;//项目数
n=5;//总投资
int temp[100][100]={{0,11,13,15,21,24},
{0,12,16,21,23,25},
{0,8,12,20,24,26} };
// f[0]=0;f[1]=11;f[2]=13;f[3]=15;f[4]=21;f[5]=24;
for(j=0;j<=n;j++)
{
// cout<<endl<<"此时应input f["<<j<<"],即 投资"<<j<<"万元给当前项目所获得的的利润:" ;
// cin>>f[j];//第一组收益
f[j]=temp[0][j];
g[j]=f[j];//第一阶段
a[1][j]=j;//第一阶段最优配额
}
for(k=2;k<=m;k++)//从第2个项目开始的各个收益
{
for(i=0;i<=n;i++)
{
t[i]=f[i];//保存上一个的值
// cin>>f[i];//当前项目投入i万元的收益
f[i]=temp[k-1][i];
a[k][i]=0;
}
//k个项目 总共投资i万元
for(i=0;i<=n;i++)
{
for(j=0;j<=i;j++)//投资从0到i万元
{
//共投资i万元
//如果投资项目k j万元+投资其他(其他默认是最优)i-j万元,
//> t[i]投资i
if(f[j]+g[i-j]>t[i])//计算当前最优值
{
t[i]=f[j]+g[i-j];
// cout<<t[i]<<endl;
a[k][i]=j;//投资i万元,得到最大利益时分配给项目k的资金数
}
}
}
for(i=0;i<=n;i++)
{
g[i]=t[i];
}
}
rest=n;
for(i=m;i>0;i--)//从第m个项目到第1个项目 遍历所有项目
{
gain[i]=a[i][rest];
rest=rest-gain[i];
}
// output(gain);
cout<<"最优方案:"<<endl;
for(i=1;i<=m;i++)
{
cout<<"项目"<<i<<"\t金额:"<<gain[i]<<endl;;
}
cout<<g[n];
}
int main()
{
invest();
return 0;
}
/*
最优方案:
项目1 金额:1
项目2 金额:1
项目3 金额:3
43
*/
背包问题
问题分析
![](https://img-blog.csdnimg.cn/fa1d82f743274eafb947b48e3341c141.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_14,color_FFFFFF,t_70,g_se,x_16)
最优子结构的证明——整体最优解包含局部最优解
![](https://img-blog.csdnimg.cn/350353303e3c45d594604670d220bd68.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_20,color_FFFFFF,t_70,g_se,x_16)
则 (y2,...,yn)是(2)式的一个最优解。
【这就是最优子结构】
![](https://img-blog.csdnimg.cn/c7ff3112240f48d1b7d43a1a0833947a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_15,color_FFFFFF,t_70,g_se,x_16)
此时
矛盾,得证。
子集合及阶段划分:
计算模型
![](https://img-blog.csdnimg.cn/41ae53134a8744a0836b311c7b3bb46d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_16,color_FFFFFF,t_70,g_se,x_16)
(2)存储
![](https://img-blog.csdnimg.cn/b3f3ac3b0e9a42fbad3ffb2d27d0824c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_11,color_FFFFFF,t_70,g_se,x_16)
第1阶段,新增物品i=1,有
![](https://img-blog.csdnimg.cn/53c2d7ccf7864fd98a5f8b1d535cc3c7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_17,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/6951360785bd4cbeb51ad2d2c8d1f25b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_20,color_FFFFFF,t_70,g_se,x_16)
【例8-3】矩阵连乘问题
内在规律:
1.保证可以连乘:列=行
2.运算次数:
【例8-3】矩阵连乘问题。
问题分析
(1)阶段划分:按矩阵多少进行划分
(2)阶段决策:有备忘的算法,影响:起点,终点,矩阵数目——跨度。为了找到分界点
数学模型
![](https://img-blog.csdnimg.cn/c052d25529aa45aa87b18214380d8787.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_14,color_FFFFFF,t_70,g_se,x_16)
思考题:如何还原最优解?
算法设计与描述
代码://递归
#include<iostream>
using namespace std;
int n=4;
int r[100]={0,5,20,50,1,100};//保存矩阵大小
int com[100][100]={0};//保存矩阵编号
int temp[100][100];//保存Mij最优计算量
int cou(int i,int j)
{
cout<<i<<"->"<<j<<endl;
if(temp[i][j]>=0)//已经计算过
{
//因为是从小到大,所以没有覆盖
return temp[i][j];
}
if(i==j)//此时 Mii 也就是同一个矩阵
{
temp[i][j]=0;//所需计算量为0
// com[i][j]=0;//此时分割点,已经在初始化时赋值了
return temp[i][j];
}
if(i+1==j)//两个矩阵相乘 结果固定
{
temp[i][j]=r[i]*r[i+1]*r[i+2];
com[i][j]=i;//此时分割点的下标 是i
return temp[i][j];
}
//其他情况 此时有多个情况
//其中一个选择,作为基准值
int x=cou(i,i)+cou(i+1,j)+r[i]*r[i+1]*r[j+1];//0增益+已知增益+新增增益
com[i][j]=i;//记录这种分割点
int y;
for(int k=i+1;k<j;k++)
{
y=cou(i,k)+cou(k+1,j)+r[i]*r[k+1]*r[j+1];//记录每种选择下的增益
if(y<x)//当前值更优化
{
x=y;
com[i][j]=k;//分割点在k+1
}
}
temp[i][j]=x;//
return temp[i][j];
}
void show(int i,int j)
{
if(i==j)
{
if(i==1)cout<<"M"<<i;
else cout<<"M"<<i;
return ;
}
cout<<"(";
show(i,com[i][j]);
cout<<"*";
show(com[i][j]+1,j);
cout<<")";
}
int main()
{
//初始化
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
{
temp[i][j]=-1;
com[i][j]=0;
}
}
//调用
cou(1,n);
for(int i=1;i<=n;i++)
{
cout<<endl;
for(int j=1;j<=n;j++)
{
cout<<i<<""<<j<<":"<<com[i][j]<<"\t";
}
}
cout<<endl;
//输出结果 递归
int x=1,y=n;
cout<<"次数:"<<temp[x][y]<<endl;
cout<<"计算顺序:";
show(x,y);
}
代码:非递归
//非递归
void fei()
{
int n=4;
int r[10]={0,5,20,50,1,100};//Mi 是矩阵ri*ri+1
int com[10][10]={0};//保存矩阵编号
int m[10][10]={0};//m[i][j]是Mi***Mj的最小乘法次数
for(int i=1;i<=n;i++)//M1 -- M4
{
m[i][i]=0;//一个矩阵
m[i][i+1]=r[i]*r[i+1]*r[i+2];//两个矩阵相乘 M1 M2
com[i][i+1]=i;//分割点是i
}
//三及以上个矩阵 Mij
for(int j=1;j<=n;j++)
// for(int i=2;i<=n;i++)//一共i个矩阵
{
for(int i=2;i<=n;i++)
// for(int j=1;j<=n;j++)
{
if(i+j-1>n)continue;
//Mj Mj+1 M..Mj+i-1
//此时Mj开始 一共i(3->n)个矩阵 前i-1个矩阵已经乘完 因此间隔点应是[j+i-2] Mj...Mj+i-2*Mj+i-1
//Mj j+i-1
m[j][j+i-1]=m[j][j]+m[j+1][j+i-1]+r[j]*r[j+1]*r[j+i];
com[j][j+i-1]=i;
// m[j][j+i-1]=m[j][j+i-1]+r[j]*r[j+i-1]*r[j+i];
// com[j][j+i-1]=j+i-2;//间隔点
int temp;
for(int k=j+1;k<=j+i-1;k++)//间隔点 下标
{
temp=m[j][k]+m[k+1][j+i-1]+r[j]*r[k+1]*r[j+i];
if(temp<m[j][j+i-1])
{
m[j][j+i-1]=temp;
com[j][j+i-1]=k;
}
}
}
}
cout<<endl<<endl<<m[1][n];
// print ("The least calculate quantity:",m[1][n]);
for (int i=1;i<=n;i++)
{
cout<<endl;
for (int j=1;j<=n;j++)
cout<<com[i][j];
}
show(1,n);
}
void fun()
{
int n=4;
int r[10]={0,5,20,50,1,100};//Mi 是矩阵ri*ri+1
int com[10][10]={0};//保存矩阵编号
int m[10][10]={0};//m[i][j]是Mi***Mj的最小乘法次数
for(int i=1;i<n;i++)
{
m[i][i]=0;//一个矩阵
m[i][i+1]=r[i]*r[i+1]*r[i+2];//两个矩阵相乘
com[i][i+1]=i+1;//分割点是i+1
}
for(int s=2;s<n;s++)//矩阵之间的跨度 或者说中间间隔几个矩阵
{
for(int i=1;i+s<n+1;i++)
{
int j=i+s;//(12 23)
m[i][j]=m[i][i]+m[i+1][j]+r[i]*r[i+1]*r[j+1];
com[i][j] = i;
for (int k=i+1;k<j;k++)
{
int t=m[i][k]+m[k+1][j]+ r[i]*r[k+1]*r[j+1];
if (t <m[i][j])
{
m[i][j] = t;
com[i][j]= k;
}
}
}
}
cout<<endl<<endl<<m[1][n];
// print ("The least calculate quantity:",m[1][n]);
for (int i=1;i<=n;i++)
{
cout<<endl;
for (int j=1;j<=n;j++)
cout<<com[i][j];
}
show(1,n);
}
思考题:
![](https://img-blog.csdnimg.cn/ea386a5b7d41452d8b350a4a239eef6d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Ie05ZG95bCP5a2m5pyf,size_17,color_FFFFFF,t_70,g_se,x_16)
1.证明具有最优子结构:
问题分析:
机器数n=5,厂子数m=3,将xi台机器给第i个厂子 效益为fi(xi) ,i=1...m
目标方程 max sum[ fi(xi)] i=1...m
s.t. sum[ xi ]=n 台机器 xi∈n xi>=0 i=1..m厂子
第一阶段:给A厂投资,g1(x)=f1(xi)
第二阶段:加入B厂,g2(x)=max{ f2(xi)+g1(x-xi) }
第三阶段:加入C厂,g3(xi)=
总结:
代码:
#include<iostream>
using namespace std;
void invest()
{
int n=5;//机器
int m=3;//厂子
int profit[10][10]={{0},{3,5,4},{7,10,6},{9,11,11},{12,11,12},{13,11,12}};
// fit[i][j] i台机器对厂子j的盈利
int machine[10][10]={0};//最大利益时,一共i台机器 给j厂machine[i][j]台
int tempinitprofit[10];//当前阶段最大利益
int tempmaxprofit[10];
int tempplan[10]={0};//当前阶段最优计划
int maxmachine[10];//最终计划
//一台厂子时
for(int i=0;i<=n;i++)
{
tempinitprofit[i]=profit[i][0];
tempmaxprofit[i]=profit[i][0];
machine[1][i]=i;
}
for(int k=2;k<=m;k++)
{
//加入厂子k
//初始化
for(int i=0;i<=n;i++)
{
tempinitprofit[i]=tempmaxprofit[i];//实际是上一阶段的最大利润
tempmaxprofit[i]=profit[i][k-1];
machine[k][i]=0;
// cout<<tempmaxprofit[i]<<endl;
}
//进行划分
for(int i=0;i<=n;i++)
{
for(int j=0;j<=i;j++)
{
if(tempmaxprofit[j]+tempinitprofit[i-j]>tempplan[i])
{
tempplan[i]=tempmaxprofit[j]+tempinitprofit[i-j];
// cout<<tempplan[i]<<endl;
machine[k][i]=j;
}
}
}
for(int i=0;i<=n;i++)
{
tempmaxprofit[i]=tempplan[i];
// cout<<tempmaxprofit[i]<<endl;
}
}
int rest=n;
for(int i=m;i>0;i--)
{
maxmachine[i]=machine[i][rest];
rest=rest-maxmachine[i];
}
//输出
cout<<"最佳方案:"<<tempmaxprofit[n]<<endl;
for(int i=1;i<=m;i++)
{
cout<<"厂子"<<(char)(i+64)<<"\t机器"<<maxmachine[i]<<"台"<<endl;
}
}
int main()
{
invest();
return 0;
}