算法导论(第三版)CH15-动态规划习题15-10投资策略规划题目实现详解(附代码)

在算法导论(第三版)第15章动态规划最后的习题中,有一道15-10投资策略规划的题目,网上相关的讲解不是很多,或者算法的时间复杂度也较高,因此将具体实现分享如下。

一、先看题目

算法导论(第三版)CH15-动态规划习题15-10投资策略规划题目实现详解(附代码)_第1张图片

二、解题思路

     这道题目其实不用动态规划,也能解的。但既然放在这里,就是给大家练习一下动态规划的思路的,我们就用动态规划的思路来解一下。先看a、b、c 前三个小题目。

第一步:最优子结构

我们假设要计算j年的投资收益,这里有一个隐含的前提,我们一定是在n种投资里面寻找当年收益率最大的那个投资种类,假设:

  1. j-1 年 的选择投资种类为 p(j-1),j-1 年底的 收益 为 e(j-1)
  2. j年投资收益最大的种类 为 p(j),

强调一点,f2>f1, 也就是说转移投资种类的成本是更高的(这也符合金融投资的实际情况)。那么根据是否转移投资,有两种情况:

1、 p(j) == p(j-1) 

此时不需要进行转移投资:j年的收益为:

        e(j) = (e(j - 1) - f1) * r(p(j), j); // r(p(j), j) 代表 j年的最大投资收益率

2、p(j) != p(j-1) 

这时就要进行对比了,分别计算转移和不转移两种情况的收益:

假设:

        t1 = (e(j - 1) - f2) * r(p(j), j);  // 转移投资的收益

        t2 = (e(j - 1) - f1) * r(p(j - 1)), j);   // 不转移的收益,此时投资种类取的是上一年的值

if t1 > t2 : 则 转移投资 ,令:

        e(j) = t1;

else:则无需转移投资,令:

        e(j) = t2;

        p(j) = p(j-1);    // 此时j年的投资种类取 j - 1 年的值。

        从上面的分析可以看出,该问题存在最优子结构,j年的投资收益只和 当年的p(j) 、去年的p(j-1) 的值 有关。以此递推,我们可以求出第2年……直到第m年的e 的值。

        初始条件:第1年的投资收益最简单,直接求出 p(1)后,因为第一年不涉及转移与否, 故第一年的投资收益为:

        e(1)=capital * r[p[1]][1]

第二步:状态转移方程

根据上面的分析,很容易就可以写出状态转移方程了:

e(j)=\begin{cases} capital * r[p[1]][1] & \text{ if } j == 1 \\ (e[j - 1] - f1) * r[p[j]][j] & \text{ if } j > 1 \&\& p(j) == p(j-1) \\ max((e[j - 1] - f1) * r[p[j - 1]][j], (e[j - 1] - f2) * r[p[j]][j]) & \text{ if } j > 1 \&\& p(j) != p(j-1) \end{cases}

第三步:定义dp数组

根据上述分析,在用动态规划思路解决本题目时,需要定义两个dp数组:

p:一维数组,用来保持每年选择的投资种类;

e:一维数组,用来保存每年底收益的值;

以上就是整个的分析思路。同时解决了 a、b 两个问题。c问题请参考下节的代码实现。

最后一个d 问题的分析,因为增加了限制条件,很容易看出,上面的最优子结构分析已经不再适用了,每年的收益只能通过暴力循环求解,因此也不再适用动态规划的解法了。

三、具体算法代码实现( Dart 语言实现)

以下是具体的实现代码,代码经过略微的优化,使用了一些小的编程技巧,以使代码更加的简洁。本代码用 Dart 语言实现,要求 dart-sdk 版本 3.0 以上。代码中注释比较清晰,不再赘述。

(List, List) findInvestStrategy(
    int capital, int kinds, int years, List> r, int f1, int f2) {
  // p:每一年选择的投资种类,ens:每年的最大化收益;
  var p = List.filled(years + 1, 0), ens = List.filled(years + 1, 0.0);

  for (var j = 1; j <= years; j++) {
    var q = 1;
    // 遍历每一个类别,寻找收益率最大的那个投资种类
    for (var i = 2; i <= kinds; i++) if (r[i][j] > r[q][j]) q = i;
    p[j] = q;

    // ens[j]表示当前年份的收益率,先假设不切换投资种类时,j年的收益是多少;
    ens[j] = switch (j) {
      1 => capital * r[q][j],
      _ => (ens[j - 1] - f1) * r[p[j - 1]][j]
    };

    // 如果q不等于p[j-1],即j年的最大化投资收益种类不等于上一年,则计算一下切换投资的收益并对比
    if (j > 1 && q != p[j - 1]) {
      // t表示计算一下切换投资的收益
      var t = (ens[j - 1] - f2) * r[q][j];
      if (t > ens[j]) {
        ens[j] = t;
      } else {
        // 意味着不用切换投资种类
        p[j] = p[j - 1];
      }
    }
  }

  return (p, ens);
}

测试如下,投资种类设为4种,年份为5年,构造r值:

void main() {
  var kinds = 4, years = 5, capital = 10000, f1 = 1000, f2 = 1300;
  var r = >[
    [0, 0, 0, 0, 0, 0],
    [0, 1.1517, 1.1360, 1.2187, 1.1289, 1.1458],
    [0, 1.1314, 1.0450, 1.1805, 1.1398, 1.1340],
    [0, 1.1609, 1.0235, 1.2591, 1.0688, 1.1298],
    [0, 1.1857, 1.0155, 1.2102, 1.0789, 1.1609]
  ];

  // r.skip(1).forEach((e) => print(e.skip(1).map((e) => e.toStringAsFixed(4))));
  var (p, ens) = findInvestStrategy(capital, kinds, years, r, f1, f2);
  print(p.skip(1));
  print(ens.skip(1).map((e) => e.toStringAsFixed(2)));
}

运行结果如下:

(4, 1, 3, 2, 2)
(11857.00, 11992.75, 13463.24, 13863.67, 14587.40)

四、时间复杂度分析

从上面分析和代码可以看出,本算法的时间复杂度为O\left ( mn \right ),m为年份,n为投资种类。

最后

1、推荐一下 Dart 语言,真的很好用,Dart3.0版本以后,增加了模式匹配等当前Rust语言很流行的很多功能,大大提升了函数式编程的能力;3.1版本发布后,经实测性能还有了巨大的提升。我日常写一些算法的代码,都是用Dart。

2、抽空在看算法导论(补大学荒废的时光),刚看到第15章。书中的例子、习题,除了部分构造数据复杂的以外,很多习题我都写了实现,如果有人需要,可以关注我,给我留言,我分享。

你可能感兴趣的:(数据结构与算法,Dart语言学习,算法,动态规划,数据结构,程序人生)