已发布·持续改进
Algorithm
动态规划(DP)通过保存并复用重叠子问题的答案,把指数级的搜索变成高效的填表计算,用于求解最优化和计数问题。
动态规划(Dynamic Programming,DP)是一种算法设计方法:把问题拆成更小的子问题,每个子问题只求解一次并记录答案,之后再遇到同一个子问题时直接取用记录的结果。它由理查德·贝尔曼在 20 世纪 50 年代系统化提出,适用于子问题相互重叠(重叠子问题),并且整体最优解可以由子问题的最优解构成(最优子结构)的情形。
许多用朴素递归或暴力枚举需要指数时间的问题,借助动态规划可以在与状态数成正比的时间内解决。文件比较工具 diff、拼写检查中的编辑距离、DNA 序列比对、语音识别中的维特比算法、数据库查询优化器的连接顺序选择等,都在使用动态规划。它也是技术面试和算法竞赛中最常考的主题之一。
学习时建议先用斐波那契数列理解记忆化搜索(自顶向下)与递推填表(自底向上)的区别,然后依次练习经典问题:零钱兑换、0/1 背包、最长递增子序列、最长公共子序列和编辑距离。每道题都先写出状态、状态转移方程、边界条件和计算顺序,手工填一张小表验证转移方程,再动手写代码。
同一个子问题多次出现时只计算一次并保存结果,把重复计算变成查表。
整体最优解必须能由子问题的最优解组合而成,这一性质是状态转移方程成立的依据。
既可以自顶向下给递归加缓存(functools.cache),也可以自底向上用循环填表。
用决定答案所需的最少参数定义状态;如果只依赖上一行,可以用滚动数组节省内存。
knapsack 用只有一行的表自底向上求解 0/1 背包问题,容量从大到小遍历,保证每件物品最多使用一次。lcs 用 functools.cache 做记忆化,自顶向下计算两个字符串的最长公共子序列长度。运行 python dynamic_programming.py 会输出 9 和 4。
dynamic_programming.py
from functools import cache
def knapsack(weights, values, capacity):
"""0/1 knapsack: best total value within capacity (bottom-up, one row)."""
best = [0] * (capacity + 1)
for w, v in zip(weights, values):
for c in range(capacity, w - 1, -1): # high to low: each item used at most once
best[c] = max(best[c], best[c - w] + v)
return best[capacity]
def lcs(a, b):
"""Length of the longest common subsequence (top-down, memoized)."""
@cache
def solve(i, j):
if i == len(a) or j == len(b):
return 0
if a[i] == b[j]:
return 1 + solve(i + 1, j + 1)
return max(solve(i + 1, j), solve(i, j + 1))
return solve(0, 0)
print(knapsack([1, 3, 4, 5], [1, 4, 5, 7], 7)) # 9
print(lcs("ABCBDAB", "BDCABA")) # 4
python dynamic_programming.py共六章,带你从安装一步步了解 动态规划 的核心概念。
在这里提问、分享经验,交流关于 动态规划 的看法。
还没有讨论。来发起第一个吧。
0 条评论
登录 · 登录后即可发表评论。
来发表第一条评论吧。