从斐波那契数列和零一背包问题,全方位了解动态规划
2023-11-09 17:40:52
动态规划,算法中的思维利器
在算法的世界里,动态规划 (Dynamic Programming) 是一门影响深远的技巧,它以其独特的方式应对具有最优子结构性质的复杂问题,将问题分解为一系列子问题,一步步解决,避免重复计算。
斐波那契数列和零一背包问题,便是动态规划的经典应用场景,它们都完美契合动态规划的解题原则。在这篇文章中,我们将深入探索这两个问题,从它们入手,全面掌握动态规划的精髓。
斐波那契数列,动态规划的入门之作
斐波那契数列,源自大名鼎鼎的“兔子问题”,其本质是计算斐波那契数列中的第 n 个数,这个数列满足这样的递推公式:
F(n) = F(n-1) + F(n-2)
其中,F(0) = 0,F(1) = 1。
递归的朴素解法:指数级时间复杂度
倘若我们采用朴素的递归方法,计算斐波那契数列中的第 n 个数,会陷入指数级的时间复杂度,这是因为每次计算 F(n) 时,都会重复计算 F(n-1) 和 F(n-2)。
动态规划的巧思:备忘录法
动态规划则能巧妙地解决这个问题,其核心思想在于利用备忘录 (Memoization) 避免重复计算。备忘录法,顾名思义,就是将已经计算过的子问题的解记录在备忘录中,当再次遇到相同的子问题时,直接从备忘录中查找即可。
def fibonacci(n):
if n == 0 or n == 1:
return n
if n in memo:
return memo[n]
memo[n] = fibonacci(n-1) + fibonacci(n-2)
return memo[n]
在这个 Python 实现中,memo 字典充当备忘录,用于存储已经计算过的斐波那契数,当需要计算 F(n) 时,先检查备忘录中是否有,如果有,则直接返回,否则计算并将其放入备忘录。如此一来,将问题分解成子问题,利用备忘录避免重复计算,即可大大降低时间复杂度。
零一背包问题,动态规划的进阶之选
零一背包问题,也称 0-1 背包问题,是动态规划中的另一大经典应用。它的本质是,给定一个背包,容量为 C,以及 n 件物品,每件物品的重量为 w[i],价值为 v[i],在不超过背包容量的情况下,如何选择装入背包的物品,使得背包的总价值最大。
递归的朴素解法:指数级时间复杂度
采用朴素的递归方式,计算零一背包问题的最优解,也会陷入指数级的时间复杂度。这是因为每当选择是否将第 i 件物品放入背包时,都需要递归考虑两种情况:放入和不放入。
动态规划的妙招:状态转移方程
动态规划的魅力在于,它可以将复杂的零一背包问题分解成一系列子问题,并通过状态转移方程,将这些子问题的解逐步积累,最终得到问题的最优解。
状态转移方程为:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])
其中,dp[i][j] 表示前 i 件物品放入容量为 j 的背包时,能够获得的最大价值。dp[i-1][j] 表示前 i-1 件物品放入容量为 j 的背包时,能够获得的最大价值。dp[i-1][j-w[i]] + v[i] 表示将第 i 件物品放入容量为 j 的背包时,能够获得的最大价值。
def knapsack(w, v, C):
n = len(w)
dp = [[0] * (C + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, C + 1):
if w[i-1] > j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1])
return dp[n][C]
在这个 Python 实现中,dp 二维数组保存了子问题的最优解。我们逐步填充 dp 数组,最终得到零一背包问题的最优解。
结语:动态规划的价值
动态规划,作为算法中的利器,不仅能够解决斐波那契数列和零一背包问题,它还广泛应用于图论、运筹学、计算机科学等多个领域。其核心在于将问题分解为一系列子问题,并通过状态转移方程,逐步积累子问题的最优解,最终得到问题的最优解。
掌握动态规划,对于算法工程师来说,是必备的技能之一。它能够帮助你在解决复杂问题时,化繁为简,找到最优解,成为算法世界的佼佼者。