动态规划之编辑距离
Posted 超级码厉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划之编辑距离相关的知识,希望对你有一定的参考价值。
先祭动态规划模版一套以及编辑距离算法的推导思路,再给出动态规划的概念与思想。
编辑距离在NLP(自然语言处理)中使用比较常见,用来比较两段文本之间的相似性。求解过程使用动态规划思路。
使用场景一:辅助翻译系统的中语料库的处理;有道字典中词条的处理。
使用场景二:智能音箱小爱同学把人语言发出的指令转换成文本后,与后台的指令集进行相似度对比,选出最接近的指令。
使用场景三:DNA序列分析。
更多 ... ...
leetcode原题入下:
Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)
You have the following 3 operations permitted on a word:
a) Insert a character
b) Delete a character
c) Replace a characte
最优子结构分析:假定dp[i][j] 表示word1的前i个字符最少要用几次编辑可以变成word2的前j个字符,有两类情况:
情况一:word1.charAt(i-1) != word2.charAt(j-1)
删除word1的第i个字符,计算word1的前i-1个字符如何变成word2的前j个字符。
dp[i][j] = dp[i-1][j] + 1
删除word2的第j个字符,计算word1的前i个字符如何变成word2的前j-1个字符。
dp[i][j] = dp[i][j-1] + 1
替换word1的第i个字符,计算word1的前i-1个字符如何变成word2的前j-1个字符。
dp[i][j] = dp[i-1][j-1] + 1
替换word2的第j个字符,计算word1的前i个字符如何变成word2的前j-1个字符。
dp[i][j] = dp[i-1][j-1] + 1
在word1的第i个字符后插入一个字符,使其等于word2的第j个字符
dp[i][j] = dp[i][j-1] + 1
在word2的第j个字符后插入一个字符,使其等于word1的第i个字符
dp[i][j] = dp[i-1][j] + 1
情况二:word1.charAt(i-1) == word2.charAt(j-1),
计算word1的前i-1个字符变成word2的前j -1个字符
dp[i][j] = dp[i-1][j-1], 即,dp[i][j] 计算结果等于上一个子结构dp[i-1][j-1]
word1的第i个字符后插入一个字符,使其等于word2的第j个字符
dp[i][j] = dp[i][j-1] + 1
word2的第j个字符后插入一个字符,使其等于word1的第i个字符
dp[i][j] = dp[i-1][j] + 1
上述两类情况总共9个计算公式有重复,可以合并成四个:
(1)dp[i][j] = dp[i-1][j-1]
(2)dp[i][j] = dp[i-1][j] + 1
(3)dp[i][j] = dp[i][j-1] + 1
(4)dp[i][j] = dp[i-1][j-1] + 1
因为要计算最小编辑次数,所以状态方程如下:
f[i][j] = Math.min(f[i - 1][j - 1], Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));
f[i][j] = Math.min(f[i - 1][j - 1] + 1, Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));
java实现如下:
public class Solution {
public int minDistance(String word1, String word2) {
if(word1 == null && word2 == null) {
return 0;
}
if(word1 == null) {
return word2.length();
}
if(word2 == null) {
return word1.length();
}
int m = word1.length();
int n = word2.length();
//中间结果
int[][] f = new int[m + 1][n + 1];
//初始化
for(int i = 1; i <= m; i++) {
f[i][0] = i;
}
for(int j = 1; j <= n; j++) {
f[0][j] = j;
}
//状态计算
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
if(word1.charAt(i - 1) == word2.charAt(j - 1)) {
f[i][j] = Math.min(f[i - 1][j - 1], Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));
} else {
f[i][j] = Math.min(f[i - 1][j - 1] + 1, Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));
}
}
}
//状态计算
return f[m][n];
}
}
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题。
动态规常见类型:
(1)坐标型动态规划
(2)序列型动态规划
(3)双序列动态规划
(4)划分型动态规划
(5)背包型动态规划
(6)区间型动态规划
动态规划基本思想:
(1)将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案存起来,以后遇到直接引用。(过程通常临时存储,所以空间复杂度较高)。
(2)动态规划算法将问题的解决方案视为一系列决策的结果,与贪婪算法不同的是,在贪婪算法中,每采用一次贪婪准则,便做出一个不可撤回的决策;而在动态规划算法中,还要考察每个最优决策序列中是否包含一个最优决策子序列,即问题是否具有最优子结构性质。
动态规划算法的有效性依赖于待求解问题本身具有的两个重要性质:最优子结构性质和子问题重叠性质。
(1)最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。
(2)子问题重叠性质。子问题重叠性质是指在用递归算法对问题进行求解时,每次产生的子问题并不总是新问题,可能会被重复计算。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的解题效率。
当我们已经确定待解决的问题需要用动态规划算法求解时,通常可以按照以下步骤设计动态规划算法:
(1)分析问题的最优解,找出最优解的性质,并刻画其结构特征;
(2)递归地定义最优值;
(3)采用自底向上 / 自顶向下的方式计算问题的最优值;
(4)根据计算最优值时得到的信息,构造最优解。
以上是关于动态规划之编辑距离的主要内容,如果未能解决你的问题,请参考以下文章