Lecture 8: Dynamic Programming¶
动态规划。
Solve sub-problems just once and save the answers in a table.
1 Example: Fibonacci Number¶
使用传统的递归方法:
C | |
---|---|
1 2 3 4 5 6 |
|
对此进行性能分析:\(T(N) \ge T(N-1) + T(N-2)\),推导出了 \(T(N) \ge F(N)\)。
问题出在,我们在递归过程中,重复计算了很多次相同的子问题。如果我们能将这些重复的子问题的解放在一张表中(或者就是简单地保存下来),那么我们就可以避免重复计算。
C | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
显然这种算法的时间复杂度低很多,为 \(O(N)\)。
2 Example: Ordering Matrix Multiplication¶
要找到一种使得开销最小的乘法顺序,我们使用这种动态规划的思想。计算任意一个矩阵链的最小乘法次数,我们可以将其分解为两个子链的最小乘法次数,但是这个分解方式就有很多种,我们需要找到最优的那种。
\[
m_{ij} = \left \{
\begin{aligned}
0 & \quad i = j \\
\min_{i \le k < j} \{ m_{ik} + m_{k+1,j} + p_{i-1}p_kp_j \} & \quad i < j
\end{aligned}
\right.
\]
3 Example: Optimal Binary Search Tree¶
需要找到一种使得平均查询时间最小的树结构,也即需要使
\[
T(N) = \sum\limits_{i=1}^{N} p_i\cdot (1+d_i)
\]
最小。一种很 Greedy 的想法就是把 freq 最大的放在根节点。这种虽然做出来不一定是最优解,但是却给我们以启示:我们根据“谁来当根节点”作为这种遍历的指标。
具体推导可以看 PPT,最后解决问题的方式是自底向上,从只有一个结点的树开始一步一步构造。
4 Example: All-Pairs Shortest Path¶
Floyd 算法(动态规划)。
与前面的例子类似,我们还是维护一个动态规划的数组。
\[
D^k[i][j]=min\{length\ of\ path\ i \rightarrow \{l \le k\} \rightarrow j\}
\]
这代表从 i 到 j 的,中间经过 k 个结点的最短路径。很容易知道当我们从 k 为 0 的时候开始遍历,最后 k 到 N-1 的时候一定得到的是最优解。
如何进行“状态转移”呢?当我们已经知道了 \(D^{k-1}\) 的信息之后:
- 如果我们不想将 k 加入最短路径,直接让 \(D^k\) 继承上一个值即可;
- 如果想,那么就是把 i 到 k 的最短路径和 k 到 j 的最短路径加起来就行了。
所以:
\[
D^k[i][j]=min\{ D^{k-1}[i][j],D^{k-1}[i][k]+D^{k-1}[k][j] \},\ k\ge 0
\]
给出代码:
C | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
\(T(N) = O(N)\).
5 Example: Product Assembly¶
第 i 个 stage 来源于上一个 stage,它可以是由别的生产线转移而来,也可以是自己这条生产线。
6 Summary¶
总结一下。
DP 的特征:
- 最优子结构
- 重叠子问题
设计 DP 方法:
- 描述最优解的样子
- 递归地定义最优值
- 以某一顺序来计算
- 最后重构解法(选)