动态规划介绍与使用
1. 算法定义
动态规划(Dynamic Programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
2. 诞生背景
动态规划诞生于20世纪50年代,由美国数学家理查德·贝尔曼提出。当时,为了解决多阶段决策问题,贝尔曼提出了动态规划这一概念。
3. 发展历程
自贝尔曼提出动态规划以来,这一方法在多个领域得到了广泛的应用和发展。以下是动态规划的主要发展历程:
- 1950年代:贝尔曼提出动态规划基本理论。
- 1960年代:动态规划在运筹学、控制理论等领域得到应用。
- 1970年代:动态规划开始应用于计算机科学领域,如算法设计。
- 1980年代至今:动态规划在人工智能、生物信息学等领域取得显著成果。
4. 特点
动态规划具有以下特点:
- 最优子结构:问题的最优解包含其子问题的最优解。
- 子问题重叠:动态规划将原问题分解为多个子问题,这些子问题不是独立的,而是重叠的。
- 无后效性:某阶段的状态一旦确定,就不受之后阶段的影响。
5. 基本原理
动态规划的基本原理是将复杂问题分解为若干个相互重叠的子问题,从最简单的子问题开始求解,并将子问题的解存储起来,避免重复计算。最后,通过子问题的解得到原问题的解。
6. 应用领域
动态规划在以下领域有广泛应用:
- 计算机科学:算法设计、字符串匹配、图像处理等。
- 经济学:资源分配、投资策略、价格决策等。
- 生物信息学:基因序列比对、蛋白质结构预测等。
7. 实例理解
以斐波那契数列为例,其递推公式为:F(n) = F(n-1) + F(n-2),其中F(0) = 0,F(1) = 1。
F(0) = 0
F(1) = 1
F(2) = F(1) + F(0) = 1
F(3) = F(2) + F(1) = 2
F(4) = F(3) + F(2) = 3
...
通过动态规划,我们可以将F(n)分解为F(n-1)和F(n-2)两个子问题,从而避免重复计算。
8. 代码示例
以下是一个使用Python实现的斐波那契数列的动态规划代码示例:
def fibonacci(n):
if n <= 1:
return n
dp = [0] * (n+1)
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(fibonacci(10))
9. 代码分步讲解
- 定义一个名为
fibonacci
的函数,参数为n
,表示求斐波那契数列的第n
项。 - 判断
n
的值,如果小于等于1,直接返回n
。 - 创建一个长度为
n+1
的列表dp
,用于存储子问题的解。 - 初始化
dp[1] = 1
,因为斐波那契数列的第二项为1。 - 使用一个for循环,从2遍历到
n
,计算每个子问题的解,并存储在dp
列表中。 - 返回
dp[n]
,即斐波那契数列的第n
项。
通过以上步骤,我们成功使用动态规划求解了斐波那契数列问题。
10. 实例理解(进阶)
下面我们将使用动态规划来解决一个相对复杂的问题:最长公共子序列(Longest Common Subsequence, LCS)问题。这个问题是寻找两个或多个序列中最长的子序列,该子序列在原序列中是按相同顺序出现的,但不要求连续。
示例:
input:str1 = 123456
str2 = 234567
output:LCS = 23456
1. 问题定义
给定两个字符串 str1
和 str2
,找出它们的最长公共子序列的长度。
2. 动态规划思路
- 创建一个二维数组
dp
,其中dp[i][j]
表示str1
的前i
个字符和str2
的前j
个字符的最长公共子序列的长度。 - 初始化
dp
的第一行和第一列为0,因为任何一个字符串与空字符串的最长公共子序列长度都是0。 - 遍历
str1
和str2
的字符,如果当前字符相等,则dp[i][j] = dp[i-1][j-1] + 1
;否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])
。 - 最终
dp[str1.length()][str2.length()]
就是两个字符串的最长公共子序列的长度。
3. 代码分段
初始化部分
def longest_common_subsequence(str1, str2):
m, n = len(str1), len(str2)
# 创建一个 (m+1)x(n+1) 的二维数组,初始化为0
dp = [[0] * (n + 1) for _ in range(m + 1)]
动态规划填表
# 填充 dp 表
for i in range(1, m + 1):
for j in range(1, n + 1):
if str1[i - 1] == str2[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]
4. 整体代码
def longest_common_subsequence(str1, str2):
# 获取两个字符串的长度
m, n = len(str1), len(str2)
# 初始化动态规划表,大小为 (m+1)x(n+1),初始值为0
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 填充动态规划表
for i in range(1, m + 1):
for j in range(1, n + 1):
# 如果当前字符相等,更新 dp 表的值
if str1[i - 1] == str2[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]
# 测试代码
str1 = "ABCBDAB"
str2 = "BDCAB"
print(longest_common_subsequence(str1, str2)) # 输出应为4,最长公共子序列是 "BCAB"
以上代码展示了如何使用动态规划来解决最长公共子序列问题。代码中的注释说明了每一部分的作用和解决问题的思路。