动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。简单的理解为:是将一个棘手的问题,分成一个个小问题,先着手解决这些小问题,最后找到解决最优解的优化过程。动态规划一般可分为线性动规,区域动规,树形动规,背包动规四类。在这里,我们主要解决背包动态规划问题。用一个示例展开:
假如你要去野营。你有一个容量为6磅的背包,需要决定该携带下面的哪些东西。其中每一样东西都有相应的价值,价值越大意味着越重要:
- 水(重3磅,价值10);
- 书(重1磅,价值3);
- 食物(重2磅,价值9);
- 夹克(重2磅,价值5);
- 相机(重1磅,价值6)。
请问携带哪些东西时价值最高?
对于这样一个问题,我们去对这些东西相互组合,算出最高价值是可以的,但是,会很浪费时间,可能会到最后越来越糊涂。所以最好的方法是,使用动态规划将问题分解为小问题,再求出最高价值。具体使用的方法是——网格法。
构建这个一个网格,将背包分成1到6磅的空间,分别考虑有书;有书和相机;有书、相机和食物;有书、相机、食物和夹克;有书、相机、食物和夹克、还有水的情况下背包容下东西的最大价值。
然后,我们就可以得到下面这个完整的网格:
这能说明什么问题呢?或者说它是怎么得来的呢?
这个表格是一个迭代的过程,随着行和列的逐步增加,考虑价值如何最大。
从中举一个例子:当背包只有1磅容积的时候,只能放一本书,此时价值为3。当背包有2磅容积的时候,有两样东西(书和相机),此时价值为9。当背包有3磅容积的时候,此时如果有三样东西(书、相机和食物),应该优化选择相机和食物,而不是书和食物,因为,它们的价值更大些。
就按照这种规则优化取东西,最终当背包为6磅时,选择相机、食物和水是最大价值的。
这就是一个简单运用动态规划解决问题的范例。但是,值得注意的是:动态规划功能强大,但是仅当分离出来的子问题都是离散的,即不依赖其他的子问题时,动态规划才是管用的。
下面,将讨论两个运用动态规划的实例:最长公共子串和最长公共子序列。
如果要识别两个单词之间的相识度,找出两个单词的最长公共子串和最长公共子序列也可以用到动态规划。首先也是用网格的方式将两个单词表示出来。比如:比较hish和fish。
最长公共子串:表示的是两个单词中连续的相同字母长度。
然后,将表格填完整。具体怎么填呢?这就要规定一个准则了。如下所示:
实现这个公式的伪代码类似于下面这样:
# 两个字母相同
if word_a[i] == word_b[j]:
cell[i][j] = cell[i-1][j-1] + 1
# 两个字母不同
else:
cell[i][j] = 0
需要注意的一点是:
对于最长公共子串问题,答案为网格中最大的数字
——
它可能并不位于最后的单元格中。
最长公共子序列:表示的是两个单词中相同的字母的长度。
比如:fosh,与fish相近还是与fort相近呢?
如果用最长公共子串的方法则是相同的,都有两个字母连续相同。但是,这里我们是不是应该一眼看出其实fish更相近一些呢,因为它有三个字母与fosh相同。
这里,我们就该使用最长公共子序列的方法。
同样,先构建网格。这个时候的规则应该如下:
用伪代码的形式表示:
# 两个字母相同
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链的相似性有很好的帮助。