动态规划
动态规划其实就是对分而治之策略的一种应用, 将一个较大的问题分解成有限个的不相关的子问题问题, 然后通过解决子问题, 不断推演出最终结果。
动态规划有一个比较直观特点: 就可以通过表格的方式去描述问题。
以下使用动态规划进行字符串最短编辑处理的一个例子,通过这个例子就可以很容易的搞懂动态规划这个算法的原理和应用。
1、字符的操作方式
字符的三种操作方式: 替换, 删除, 增加。
举个例子:
替换
abc -> abe
里面就需要将c 提换成e, 这里需要的操作次数是1。
删除
abc -> ab
里面就需要将c 删除, 这里需要的操作次数是1.
增加
a -> ab
里面就需要增加字符b, 这里需要的操作次数是1.
从上面的例子可以看到, 其实两个字符串即便互相交换, 它们的最短操作距离是一样的。
2、使用动态规划解决最短编辑距离问题
. 将 adceg --> abcfg
步骤一: 初始化表格
. 首先, 我们根据比较字符串的长度新建一个 (m+1)x(n+1) 的二维表格, 这个例子中, 这个表格就是6x6, 当然, 需要比较字符串的长度不需要相等.,
. 然后, 初始化首行与首列的数据
其实,初始化的原理是和之前说的编辑逻辑保持一致的, 我们先看第一行:
.Null 表示空串, 第0,0 坐标的值是0, 表示, "空串" --> "空串" 无需任何编辑操作
.0,1 坐标的值是1, 表示从"空串" --> "a", 只需要最少1次编辑操作.
.0,2 坐标的值是2, 表示从"空串" --> "ab", 只需要最少2次编辑操作.
同理可得, 第一行剩下的值分别是3,4,5
.同样地, 我们也可以得到第一列的值分别是 0,1,2,3,4,5
步骤二: 解决字符相等的情况
.完成了初始化后, 我们尝试填充 1, 1 坐标的值
.由于第一行的字母是a, 第一列的字母也是a, 两者相等,所以我们只需要将(i-1, j-1)也就是(0,0) 的值直接复制过来, 也就是说, (1,1) 的值是0, 表示无需任何编辑操作.如下图所示
步骤三: 解决字符不相等的情况:
.好了, 我们继续填充(1,2) 坐标的值:
.i 对应的值依旧是a, j 对应的值b, 但两者不相等,这个时候, 需要我们就取 replace, insert, remove 操作对应的坐标值中的最小值. 这么说可能比较抽象, 我们先从微观上观察下每一个种操作对应的坐标值:
假设当前的坐标是i,j (i>0, j >0). 那么:
insert 操作对应的坐标值是(i, j-1)
replace 操作对应的坐标值是 (i-1, j -1)
remove 操作对应的坐标值是 (i-1, j)
我们需要做的,就是将这三种操作的最小值找出来, 然后做 +1 操作, 以 (1,2) 为例,填的值就是1:
步骤四: 填充剩余表格
.好了, 到目前为止, 所有的需要分析的步骤就完成了, 剩下就只需要填充后面的值,最后,你会发现2 就是最终的求解.
.动态规划一个很大的优势就是可以通过这个表格找出任意两个子串的最短编辑距离, 比如: abc --> adc 的最短编辑距离就是1
最终的算法实现(Python)就非常简单了:
def min_dest(i, j, arr): return min(arr[i-1,j-1], min( arr[i-1,j], arr[i,j-1])) def find_min_edit_distance(str1, str2): arr = [len(str1)][len(str2)] for i in range(len(str1)): for j in range(len(str2)): if i == 0: #初始化第0行 a[i][j] = j continue if j == 0: #初始化第0列 a[i][j] = i continue if str1[i] == str2[j]: #字符相等, 直接复制 a[i][j] = a[i-1][j-1] else: #字符不等, 去最小值. a[i][j] = min_dest(i, j, arr) return arr[len(str1)-1][len(str2)-1]
从这个例子可以看到: 动态规划的实现一点都不难, 难是难在要识别问题能通过动态规划去解决. 我认为这个过程是需要不断积累经验的过程.