7 动态规划思想的应用

在前一篇中,我们深入探讨了网络流算法的基础,理解了如何通过网络的表现来解决复杂的问题。今天,我们将转向另一重要领域:动态规划(Dynamic Programming, DP),并探讨动态规划思想的实际应用。

动态规划是一种用于解决最优化问题的算法设计范式,通过将问题分解成更小的子问题,避免重复计算,来提高效率。在本篇中,我们将关注如何应用动态规划的思想来处理复杂问题,而不仅仅是单一的动态规划问题。

动态规划思想的核心

动态规划的核心在于“最优子结构”和“重叠子问题”。它的具体应用可以通过以下几个步骤来概括:

  1. 定义状态:明确我们要解决的问题的状态表示。
  2. 状态转移:建立状态之间的关系,找出如何从子问题得到原问题的解。
  3. 边界条件:确定最小子问题的解,并作为递推的基础。
  4. 优化:通过记忆化搜索(Memoization)或表格法(Tabulation)来缓存中间结果。

应用案例

下面将以几个经典应用来展示动态规划思想的具体应用。

案例一:背包问题

问题描述:给定一组物品,每个物品有自己的重量和价值,目标是在不超过背包容量的情况下,选择物品使得总价值最大。

状态定义
dp[i][j] 表示前 i 个物品放入容量为 j 的背包中获得的最大价值。

状态转移
对于每个物品 i

  • 如果不放入背包:dp[i][j] = dp[i-1][j]
  • 如果放入背包:dp[i][j] = dp[i-1][j-w[i]] + v[i]

则有

$$
dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]) \quad \text{若} , j \geq w[i]
$$

边界条件

  • i=0j=0 时,dp[0][j] = 0dp[i][0] = 0

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def knapsack(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]

for i in range(1, n + 1):
for j in range(capacity + 1):
if j < weights[i-1]:
dp[i][j] = dp[i-1][j] # 不放入物品
else:
dp[i][j] = max(dp[i-1][j], dp[i-1][j - weights[i-1]] + values[i-1]) # 放入物品

return dp[n][capacity]

# 示例
weights = [1, 2, 3]
values = [10, 15, 40]
capacity = 6
print(knapsack(weights, values, capacity)) # 输出 55

案例二:编辑距离

问题描述:给定两个字符串 word1word2,计算将 word1 转换为 word2 所需的最少操作次数(插入、删除、替换)。

状态定义
dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少操作数。

状态转移

  1. 插入操作:dp[i][j-1] + 1
  2. 删除操作:dp[i-1][j] + 1
  3. 替换操作:dp[i-1][j-1] + 1(若字符不同)或 dp[i-1][j-1](若字符相同)

状态转移式为:

$$
dp[i][j] = \begin{cases}
dp[i-1][j] + 1 & \text{(删除)} \
dp[i][j-1] + 1 & \text{(插入)} \
dp[i-1][j-1] + 1 & \text{(替换)} & \text{若 word1[i-1] \neq word2[j-1]} \
dp[i-1][j-1] & \text{若 word1[i-1] = word2[j-1]}
\end{cases}
$$

边界条件

  • dp[0][j] = j(将空字符串转换为 word2 所需的操作数)
  • dp[i][0] = i(将 word1 转换为空字符串所需的操作数)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def min_distance(word1, word2):
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]

for i in range(m + 1):
for j in range(n + 1):
if i == 0:
dp[i][j] = j # 如果word1为空,需要j次插入
elif j == 0:
dp[i][j] = i # 如果word2为空,需要i次删除
else:
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1] # 字符相同,不需要操作
else:
dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1) # 取三者最小

return dp[m][n]

# 示例
word1 = "horse"
word2 = "ros"
print(min_distance(word1, word2)) # 输出 3

总结

在本篇中,我们探讨了动态规划的核心思想及其应用,具体通过背包问题和编辑距离问题进行了详细分析。动态规划不仅仅是一个计算问题的工具,它更是一种思考问题和结构问题的方式。通过不断地分解和重构问题,我们得以更高效地找到解决方案。

在下一篇中,我们将进一步深入动态规划的经典问题解法,探索更多动态规划领域的挑战与解决思路。希望您能继续关注这一系列教程,提升您的算法能力。

7 动态规划思想的应用

https://zglg.work/algorithm-one/7/

作者

IT教程网(郭震)

发布于

2024-08-11

更新于

2024-08-12

许可协议

分享转发

交流

更多教程加公众号

更多教程加公众号

加入星球获取PDF

加入星球获取PDF

打卡评论