动态规划
动态规划 就是把问题分为若干个相互独立的子问题,子问题得到解决之后,总问题也随之解决。
哪些问题可以使用动态规划呢?
- 在给定的约束条件下,求得最优解;
- 问题可以分解为若干个相互独立的子问题。
动态规划的特点:
- 每一种动态规划的方案都可以绘制成网格;
- 每个单元格就是子问题;
- 单元格中的值通常就是你需要优化的;
# 背包问题 ---- **背包问题:** 应该如何选择物品,才能使得背包的价值最大。 假设一个小偷要到商场偷东西,背包的容量为 $4$ 磅,他该如何选择商品才能使得背包装的价值最大。商场里可偷的物品列表如下:
针对背包问题,我们首先要解决三个问题?
- 第一:该问题是否可以使用动态规划的思想?
- 第二:如何用网格来构建该问题?
- 第三:如何把该问题分解为若干个子问题?
第一:改问题是否可以使用动态规划思想?
- 约束条件: 背包的容量;
- 优化目标: 如何选择使得背包的价值最大;
- 结论:可以使用动态规划思想
**第二:如何用网格来构建该问题? **
我们建立一个网格:横向是不同容量的背包(以最小尺寸为间距,例如上例中以
0.5
0.5
0.5 磅为间距);列向为不同的物品。
单元格的含义: 当前容量下包含的最大价值物品。初始化为
0
0
0,表示没有装任何东西。
单元格价值的计算公式如下:
- 在第一行 Y Y Y 中,只有 4 4 4 磅的背包才能装下 Y Y Y,所以 c e l l [ Y ] [ 4 ] = 3000 cell[Y][4] = 3000 cell[Y][4]=3000;
- 在第二行
B
B
B 中,
3
3
3 磅和
3.5
3.5
3.5磅的背包都可以装的下
B
B
B,所以
c
e
l
l
[
B
]
[
3
]
=
2000
、
c
e
l
l
[
B
]
[
3.5
]
=
2000
cell[B][3] = 2000、cell[B][3.5]=2000
cell[B][3]=2000、cell[B][3.5]=2000;
4
4
4 磅的背包已经装上了
Y
Y
Y,我们需要思考是否需要更新
4
4
4 磅的背包。根据公式,我们需要计算如下结果,并取最大值;
- 上一单元格的值 c e l l [ Y ] [ 4.0 ] = 3000 cell[Y][4.0] = 3000 cell[Y][4.0]=3000;
- 当前商品价值 + 剩余容量商品价值: 2000 + c e l l [ Y ] [ 4.0 − 3.0 ] = 2000 2000 + cell[Y][4.0-3.0] = 2000 2000+cell[Y][4.0−3.0]=2000
- 综合上述结果:不替换 4 4 4 磅背包中的物品;
- 在第三行
G
G
G 中,能装的下
G
G
G 有
1.0
,
1.5
,
2.0
,
2.5
1.0, 1.5, 2.0, 2.5
1.0,1.5,2.0,2.5 磅的背包,所以
c
e
l
l
[
G
]
[
1.0
]
=
1500
、
c
e
l
l
[
G
]
[
2.0
]
=
1500
、
c
e
l
l
[
G
]
[
2.5
]
=
1500
cell[G][1.0] = 1500、cell[G][2.0] = 1500、cell[G][2.5] = 1500
cell[G][1.0]=1500、cell[G][2.0]=1500、cell[G][2.5]=1500;我们现在需要考虑
3.0
、
3.5
、
4.0
3.0、3.5、4.0
3.0、3.5、4.0 磅的背包是否需要替换;
- 是否需要替换
3.0
3.0
3.0 的背包;
- 上一单元格价值 c e l l [ B ] [ 3.0 ] = 2000 cell[B][3.0] = 2000 cell[B][3.0]=2000;
- 当前商品价值 + 剩余容量商品价值: 1500 + c e l l [ B ] [ 3.0 − 1 ] = 1500 1500 + cell[B][3.0 - 1] = 1500 1500+cell[B][3.0−1]=1500;
- 综合上述结果:不替换 3.0 3.0 3.0 磅的背包;
- 是否需要替换
3.5
3.5
3.5 的背包;
- 上一单元格价值 c e l l [ B ] [ 3..5 ] = 2000 cell[B][3..5] = 2000 cell[B][3..5]=2000;
- 当前商品价值 + 剩余容量商品价值: 1500 + c e l l [ B ] [ 3.5 − 1 ] = 1500 1500 + cell[B][3.5 - 1] = 1500 1500+cell[B][3.5−1]=1500;
- 综合上述结果:不替换 3.5 3.5 3.5 磅的背包;
- 是否需要替换
4.0
4.0
4.0 的背包;
- 上一单元格价值 c e l l [ B ] [ 4.0 ] = 3000 cell[B][4.0] = 3000 cell[B][4.0]=3000;
- 当前商品价值 + 剩余容量商品价值: 1500 + c e l l [ B ] [ 4.0 − 1 ] = 3500 1500 + cell[B][4.0 - 1] = 3500 1500+cell[B][4.0−1]=3500;
- 综合上述结果:替换 4.0 4.0 4.0 磅的背包,该背包现在装有 B 、 G B、G B、G,总价值 3500 3500 3500;
- 是否需要替换
3.0
3.0
3.0 的背包;
- 后续的第 4 、 5 、 6 4、5、6 4、5、6 做类似于上述操作,最终的结果如下:
第三:如何把该问题分解为若干个子问题?
我们把 4 4 4 磅的背包问题转化为了: 0.5 0.5 0.5 磅、 1 1 1 磅、 1 1 1 磅、 1.5 1.5 1.5 磅的背包问题。
此题中算法复杂度为: O ( n 商 品 个 数 ∗ n 最 小 背 包 个 数 ) O(n_{商品个数} * n_{最小背包个数}) O(n商品个数∗n最小背包个数)
最长公共字串
最长公共字串 就是求两个字符串最长的公有字串。例如:字符串 VISTA
和字符串 HISH
,其公共最长公共字串为 IS
。
针对最长公共子串问题,我们首先要解决三个问题?
- 第一:该问题是否可以使用动态规划的思想?
- 第二:如何用网格来构建该问题?
- 第三:如何把该问题分解为若干个子问题?
第一:该问题是否可以使用动态规划的思想?
- 约束条件: 只能在两个字串之间;
- 优化目标: 求得两个字串的最长公共子串;
** 第二:如何用网格来构建该问题?**
把待匹配的字符串设为横列和纵列,如下图所示:
单元格的含义为:在当前两个子串的情况下,当前字符是第几个连续字符;
网格中最大的值即为两个字符串最长公共字串的长度。
上述公式的伪代码:
if word_a[i] == word_b[j]:
cell[i][j] == cell[i-1][j-1] +1 # 两个字母相同
else:
cell[i][j] = 0 # 两个字母不同
第三:如何把该问题分解为若干个子问题?
最长公共子串问题是如何利用动态规划的思想的呢?我们把两个字符串的匹配分成字符的匹配,然后对匹配成功的字符进行拼接,例如,上例中网格中的红线区域。
最长子序列
什么叫最长公共子序列?两段字符串中都有的子序列字母数,它是两个字符串中多个公共子串长度之和。
最长公共子序列可以用来比较两个字符串的相似程度。例如:一个人不小心输入了 fosh
,而他原本想输入的却是:fish
或者 fort
?可以通过最长子序列来判断用户到底是想输入哪一个?
如何利用动态规划来解决最长子序列问题呢?还是以上述例子为例:
单元格的含义为: 在当前子串的情况下,
单元格的计算步骤如下:
- 如果两个字母不同,当前单元格 = 上方或者左方邻居中较大的那个;
- 如果两个字母相同,当前单元格 = 左上方单元格 + 1 1 1;
伪代码如下:
if word_a[i] == word_b[j]: # 两个字母相同
cell[i][j] = cell[i-1][j-1] + 1
else:
cell[i][j] = max(cell[i-1][j], cell[i][j-1])
上述例子的示意图如下:
动态规划算法的应用
- 生物学家根据最长公共序列来确定DNA链的相似性,进而判断两种动物或疾病有多相似。最长公共序列还被用来寻找多发性硬化症治疗方案。
- 你使用过诸如
git diff
等命令吗?它们指出两个文件的差异,也是使用动态规划实现的。 - 前面讨论了字符串的相似程度。 编辑距离(levenshtein distance)指出了两个字符串的相似程度,也是使用动态规划计算得到的。编辑距离算法的用途很多,从拼写检查到判断用户上传的资料是否是盗版,都在其中。
- 你使用过诸如
Microsoft Word
等具有断字功能的应用程序吗?它们如何确定在什么地方断字以确保行长一致呢?使用动态规划!