09基于动态规划的投资问题
简述动态规划
动态规划:
动态规划(Dynamic Programming)是运筹学的一个分支,是一种使用多阶段决策过程的最优的通用方法。它不是某种具体的算法,而是算法设计技术,对求出目标函数在一定约束条件下的极值这样的问题,DP提供了高效的解决思路。
用DP解决的问题通常具有以下两个特征:
1.问题由交叠的子问题构成,将子问题记录在表中,可避免重复计算。
2.问题满足最优化法则。
举个有关DP的简单例子:
初学程序设计的时候,可能多数人都遇到过“爬楼梯问题”。给定几种合规的走法(如只允许1格1爬、2格1爬和3格1爬),爬n格(如30格)楼梯共有几种爬法。
不少人的第一选择是递归法:
int fun(int n){
if(n==1) return 1;//1
else if(n==2) return 2;//1+1、2
else if(n==3) return 4;//1+1+1、1+2、2+1、3
return fun(n-1)+fun(n-2)+fun(n-3);
}
但是,递归法的效率显然不高,存在大量的重复计算。比如计算fun(5)和fun(6)时,都重复计算了fun(4)的结果,当然,递归算法整个程序执行过程,造成的冗余重复更多。
有什么办法可以减少重复计算,最好使每一个fun(k) (k∈[1,n]) 至多计算一次?
采用动态规划的思想!将每次计算得到的值保存起来,供后面的计算直接调用,这就大幅度提高了运算效率。
#define N 31
int funDP(int n){
int a[N];
a[1]=1;a[2]=2;a[3]=4;
for(int i=4;i<=n;i++){
a[i]=a[i-1]+a[i-2]+a[i-3];
}
return a[n];
}
易得,funDP()=O(n). 动态规划的妙处值得用心去体会。
1.问题
本文主要讨论基于DP的投资问题。
问题的一般性描述:
- 设有 m 元钱,n 项投资,函数 fi(x) 表示将 x 元投入第 i 项项目所产生的效益,i=1,2,…,n.
- 问:如何分配这 m 元钱,使得投资的总效益最高?
组合优化问题,假设分配给第 i 个项目的钱数是 xi,问题描述为:
- 目标函数:max { f1(x1) + f2(x2)+ … + fn(xn)},
- 约束条件:x1 + x2 + … + xn = m , xi ∈ N.
2.解析
设 fk(x) = d 表示:x 万元投给第 k 个项目的效益为 d.
设 Ai(x) = a 表示:新加第i个项目,共分配 x 万元时,第i项分配了a元;
设 Fk(x) = s 表示:x 万元投给前 k 个项目最大效益为 s.
递推方程:Fk(x) = max{ fk ( Ak ) + Fk-1(x - Ak) }, k = 1,2,…,n.
边界条件:F1(x) = f1(x), Fk(0) = 0, k = 1,2,…,n.
举个栗子
假设各项目投资金额与生产效益之间的关系如下表:
总投资金额 m=5 万元, 项目数 n=4 , 单项目分配额 x ∈ [0,5], 如何在约束条件下,得到投资利益最大化?
根据实例逐一推导:
-
k=1, x ∈ [0,5], 求 F1(x),即求x万元投给前1个项目的最大效益
x = 0:F1(0) = 0;
x = 1(前1个项目共分配1元):F1(1) = f1(1) = 11;
x = 2:F1(2) = f1(2) = 12;
x = 3:F1(3) = f1(3) = 13;
x = 4:F1(4) = f1(4) = 14;
x = 5:F1(5) = f1(5) = 15. -
k=2, x ∈ [0,5], 求 F2(x),即求x万元投给前2个项目的最大效益
x = 0:F2(0) = f2(0) + F1(0) = 0.
x = 1(前2个项目共分配1元):
A2(1)=1(前2项共分配1万元时给第 2 项分配1 元), F2(1) = f2(1)+F1(0) = 0;
A2(1)=0(前2项共分配1万元时给第 2 项分配0 元), F2(1) = f2(0)+F1(1) = 11.
比较得 F2(1)<–max{ F2(1) } = 11.
x = 2:
A2(2)=2, F2(2) = f2(2)+F1(0) = 5;
A2(2)=1, F2(2) = f2(1)+F1(1) = 11;
A2(2)=0, F2(2) = f2(0)+F1(2) = 12.
比较得 F2(2)<–max{ F2(2) } = 12.
x = 3:
A2(3)=3, F2(3) = f2(3)+F1(0) = 10;
A2(3)=2, F2(3) = f2(2)+F1(1) = 16;
A2(3)=1, F2(3) = f2(1)+F1(2) = 12;
A2(3)=0, F2(3) = f2(0)+F1(3) = 13.
比较得 F2(3)<–max{ F2(3) } = 16.
x = 4:
A2(4)=4, F2(4) = f2(4)+F1(0) = 15;
A2(4)=3, F2(4) = f2(3)+F1(1) = 21;
A2(4)=2, F2(4) = f2(2)+F1(2) = 17;
A2(4)=1, F2(4) = f2(1)+F1(3) = 13;
A2(4)=0, F2(4) = f2(0)+F1(4) = 14.
比较得 F2(4)<–max{ F2(4) } = 21.
x = 5:
A2(5)=5, F2(5) = f2(5)+F1(0) = 20;
A2(5)=4, F2(5) = f2(4)+F1(1) = 26;
A2(5)=3, F2(5) = f2(3)+F1(2) = 22;
A2(5)=2, F2(5) = f2(2)+F1(3) = 18;
A2(5)=1, F2(5) = f2(1)+F1(4) = 14;
A2(5)=0, F2(5) = f2(0)+F1(5) = 15.
比较得 F2(5)<–max{ F2(5) } = 26. -
k=3, x ∈ [0,5], 求 F3(x),即求x万元投给前3个项目的最大效益
x = 0:F3(0) = f3(0) + F2(0) = 0.
x = 1(前3个项目共分配1元):
A3(1)=1(前3项共分配1万元时给第 3 项分配1 元), F3(1) = f3(1)+F2(0) = 2;
A3(1)=0(前3项共分配1万元时给第 3 项分配0 元), F3(1) = f3(0)+F2(1) = 11.
比较得 F3(1)<–max{ F3(1) } = 11.
x = 2:
A3(2)=2, F3(2) = f3(2)+F2(0) = 10;
A3(2)=1, F3(2) = f3(1)+F2(1) = 13;
A3(2)=0, F3(2) = f3(0)+F2(2) = 12.
比较得 F3(2)<–max{ F3(2) } = 13.
x = 3:
A3(3)=3, F3(3) = f3(3)+F2(0) = 30;
A3(3)=2, F3(3) = f3(2)+F2(1) = 21;
A3(3)=1, F3(3) = f3(1)+F2(2) = 14;
A3(3)=0, F3(3) = f3(0)+F2(3) = 16.
比较得 F3(3)<–max{ F3(3) } = 30.
x = 4:
A3(4)=4, F3(4) = f3(4)+F2(0) = 32;
A3(4)=3, F3(4) = f3(3)+F2(1) = 41;
A3(4)=2, F3(4) = f3(2)+F2(2) = 22;
A3(4)=1, F3(4) = f3(1)+F2(3) = 18;
A3(4)=0, F3(4) = f3(0)+F2(4) = 21.
比较得 F3(4)<–max{ F3(4) } = 41.
x = 5:
A3(5)=5, F3(5) = f3(5)+F2(0) = 40;
A3(5)=4, F3(5) = f3(4)+F2(1) = 43;
A3(5)=3, F3(5) = f3(3)+F2(2) = 42;
A3(5)=2, F3(5) = f3(2)+F2(3) = 26;
A3(5)=1, F3(5) = f3(1)+F2(4) = 23;
A3(5)=0, F3(5) = f3(0)+F2(5) = 26.
比较得 F3(5)<–max{ F3(5) } = 43. -
k=4, x ∈ [0,5], 求 F4(x),即求x万元投给前4个项目的最大效益
由于项目总数为4,此时可以只算x=5的情况,直接得出此问题的最大收益情况。
x = 5:
A4(5)=5, F4(5) = f4(5)+F3(0) = 24;
A4(5)=4, F4(5) = f4(4)+F3(1) = 34;
A4(5)=3, F4(5) = f4(3)+F3(2) = 35;
A4(5)=2, F4(5) = f4(2)+F3(3) = 51;
A4(5)=1, F4(5) = f4(1)+F3(4) = 61;
A4(5)=0, F4(5) = f4(0)+F3(5) = 43.
比较得 F4(5) <-- max{ F4(5) } = 61.
由此可知,F4(5) = 61:此结果为最大收益。5万元分配4个项目最大收益61万元。
A1(1)=1: 共 1 万,1 万分配给第 1 个项目,剩余 5-1=4 万;
A2(1)=0: 共 1 万,0 万分配给第 2 个项目,剩余 4-0=4 万;
A3(4)=3: 共 4 万,3 万分配给第 3 个项目,剩余 4-3=1 万;
A4(5)=1:共 5 万,1 万分配给第 4 个项目,剩余 1-1=0 万。
3.设计
#define N 5//项目数
#define M 6//投资总额
//aItem[3][2]表示共投资2万元第3个项目的投资金额,用于追溯路径
double aItem[N][M] = {0};
//Fsum[3][2]表示前3个项目共投资2万元的最大收益
double Fsum[N][M] = {0};
double investMax(double f[N][M]) {
//前1个项目的情况,即边界
for i ← 0 to M-1
do
Fsum[1][i] ← f[1][i];
aItem[1][i] ← i;
end for
//第k个项目
for k ← 2 to N-1
//k个项目共分配m万元
for m ← 1 to M-1
max ← -1,temp ← 0;
//第k个项目分配a万元
for a ← 0 to m
if (f[k][a] + Fsum[k-1][m - a] > max) then
do
max ← f[k][a] + Fsum[k-1][m - a];
temp ← a;
end if
end for a
Fsum[k][m] ← max;
aItem[k][m] ← temp;
end for m
end for k
return Fsum[N-1][M-1];
}
4.分析
算法复杂度为:n*((m+1+2)*m/2)=n*m*(m+3)/2.
investMax(n,m)=O(nm2).
5.源码
#include<iostream>
#define N 5//项目数
#define M 6//投资总额
using namespace std;
//aItem[3][2]表示共投资2万元第3个项目的投资金额,用于追溯路径
double aItem[N][M] = {0};
//Fsum[3][2]表示前3个项目共投资2万元的最大收益
double Fsum[N][M] = { 0 };
double investMax(double f[N][M]) {
//前1个项目的情况,即边界
for (int i = 0; i <= M-1; i++) {
Fsum[1][i] = f[1][i];
aItem[1][i] = i;
}
//第k个项目
for (int k = 2; k <= N-1; k++) {
//k个项目共分配m万元
for (int m = 1; m <= M-1; m++) {
double max = -1,temp=0;
//第k个项目分配a万元
for (int a = 0; a <= m; a++) {
if (f[k][a] + Fsum[k-1][m - a] > max) {
max = f[k][a] + Fsum[k-1][m - a];
temp = a;
}
}
Fsum[k][m] = max;
aItem[k][m] = temp;
}
}
return Fsum[N-1][M-1];
}
void printInfo() {
int index = M - 1;
for (int i = N - 1; i > 0; i--) {
cout << "第" << i << "个项目,投资" << aItem[i][index] << "万元" << endl;
index -= aItem[i][index];
}
}
int main() {
double f[N][M] = {//f[1][2]表示投第1个项目2万元所产生的效益
{0, 0, 0, 0, 0, 0},//没有第0个项目
{0,11,12,13,14,15},
{0, 0, 5,10,15,20},
{0, 2,10,30,32,40},
{0,20,21,22,23,24}
};
cout << M-1 <<"万元投资"<<N-1<<"项目最大收益为:" << investMax(f) << endl;
printInfo();
return 0;
}