动态规划算法原理
动态规划(Dynamic Programming,简称DP)是一种用于解决具有重叠子问题和子结构性质的问题的算法策略。其核心思想是将复杂问题分解为更小的子问题,通过保存子问题的解来避免重复计算,从而提高效率。
核心要素
- 重叠子问题:在求解过程中,很多子问题会被多次计算。动态规划通过存储这些子问题的解来避免重复计算。
- 子结构:问题的解可以由其子问题的解构造出来。
基本步骤
- 定义状态:明确问题的阶段和每个阶段的状态。
- 状态转移方程:建立从一个状态到另一个状态的转移关系。
- 初始化:确定初始状态的值。
- 计算顺序:按照一定的顺序(如自底向上)计算所有状态的值。
- 返回结果:根据最终状态的值得到问题的解。
经典案例
1. 斐波那契数列
问题描述:计算斐波那契数列的第n项,斐波那契数列的定义为:F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2) (n ≥ 2)。
动态规划解法:
- 定义状态:dp[i]表示斐波那契数列的第i项。
- 状态转移方程:dp[i] = dp[i-1] + dp[i-2]。
- 初始化:dp[0] = 0, dp[1] = 1。
- 计算顺序:从i = 2开始,依次计算dp[i]的值。
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
dp = [0] * (n + 1)
dp[0] = 0
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
2. 0-1背包问题
问题描述:给定n种物品和一个容量为W的背包,每种物品有一个重量和一个价值,在不超过背包容量的情况下,如何选择物品使得背包中的物品总价值。
动态规划解法:
- 定义状态:dp[i][j]表示前i种物品中选取若干件放入容量为j的背包中所能获得的价值。
- 状态转移方程:
- 如果不选择第i种物品,dp[i][j] = dp[i-1][j]。
- 如果选择第i种物品,dp[i][j] = dp[i-1][j-w[i]] + v[i](其中w[i]是第i种物品的重量,v[i]是第i种物品的价值)。
- 取两者的值,dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])。
- 初始化:dp[0][j] = 0(没有物品时,价值为0),dp[i][0] = 0(背包容量为0时,价值为0)。
- 计算顺序:从i = 1到n,j = 1到W,依次计算dp[i][j]的值。
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(1, capacity + 1):
if j >= weights[i-1]:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i-1]] + values[i-1])
else:
dp[i][j] = dp[i-1][j]
return dp[n][capacity]
3. 最长公共子序列(LCS)
问题描述:给定两个字符串,找出它们的最长公共子序列的长度。
动态规划解法:
- 定义状态:dp[i][j]表示字符串A的前i个字符和字符串B的前j个字符的最长公共子序列的长度。
- 状态转移方程:
- 如果A[i-1] == B[j-1],dp[i][j] = dp[i-1][j-1] + 1。
- 否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
- 初始化:dp[0][j] = 0,dp[i][0] = 0。
- 计算顺序:从i = 1到len(A),j = 1到len(B),依次计算dp[i][j]的值。
def longest_common_subsequence(A, B):
m = len(A)
n = len(B)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
动态规划是一种强大的算法策略,适用于解决具有重叠子问题和子结构性质的问题。通过合理地定义状态、建立状态转移方程,并按照一定的顺序计算状态的值,可以高效地解决问题。斐波那契数列、0-1背包问题和最长公共子序列是动态规划的经典案例,它们展示了动态规划在不同类型问题中的应用。
(www.nzw6.com)