算法笔记:编辑距离
来源:互联网 发布:apache服务器 ubuntu 编辑:程序博客网 时间:2024/06/06 22:01
算法笔记:编辑距离
原题:
给出两个单词word1和word2,计算出将word1 转换为word2的最少操作次数。
你总共三种操作方法:
- 插入一个字符
- 删除一个字符
- 替换一个字符
给出 work1=”mart” 和 work2=”karma”
返回 3
下面是两种思路实现,自顶向上方法和自顶向下方法
1.自底向上方法实现
不如将问题简化下,化成规模比较小的问题。
首先,我们先考虑两个空串,那么当然就会出现0次。
若其中有一个为空串,另外一个为单字符的话,若A为空的话,只要执行一次插入操作,就能达到目的。反之只要执行一次删除次数操作。
对于非空串来说,我们先考虑只有单字符的A和B,不相等的话就只需要执行一次替换操作,就可以到达目的。
我们将上面操作视为单字符操作
对于双字符的情况,它可以视作单字符完成后再进行单字符操作的情况。对于有三个字符的,同样视作双字符操作完成后再执行单字符判断操作的情况。以此类推,那么对于任意长度n的字符串,都可以看成n-1字符串完成后再进行单字符操作。
这是一种动态规划的算法。于是我们就可以使用一个result[i][j]数组来表示字符串A的从起始到i的子串到匹配字符串B从起始到j的子串所需要进行的操作数。
那么问题来了,我们应该如何表示单字符操作呢?
正如上面定义所说的。当A为空的话,那么就执行插入。那什么时候会出现这么一种情况?
当A的长度比B要小的时候,那么我们就需要进行插入操作。
当我们用result[i][j]形式表示该操作的时候,就表示为result[i][j] = result[i][j-1]+1
(为什么这里是result[i][j-1] 而不是result[i - 1][j]?
因为我们result[i][j-1]表示A的前i个和B的前j-1的已经匹配好的了,所以当j-1加1后,i就比较小了。所以就需要进行插入操作。
)
同理,当A的长度比B要大的时候,我们就要需要删除操作
表示为result[i][j] = result[i-1][j] + 1;
那么替换呢?
很简单,两个一样长,如果不相等就替换。
表示为result[i][j] = result[i-1][j-1] + x(x = 0 or 1)
我们可以用下图来表示他们之间的关系
迭代算法
public class Solution { public int minDistance(String word1, String word2) { int[][] result = new int[word1.length()+1][word2.length()+1]; //初始化数组 for(int i = 0 ; i <= word1.length() ; i++){ result[i][0] = i; } for(int j = 0 ; j <= word2.length() ; j++){ result[0][j] = j ; } for(int i = 1 ; i <= word1.length() ; i++){ for(int j = 1; j <= word2.length() ;j++){ // 单字符操作 if(word1.charAt(i-1) == word2.charAt(j-1)){ result[i][j] = result[i-1][j-1]; } else { result[i][j] = min(result[i-1][j-1],result[i-1][j], result[i][j-1]) + 1; } } } return result[word1.length()][word2.length()]; } public int min(int t1,int t2,int t3){ int min = t1 > t2? t2 : t1; min = min > t3? t3 : min; return min; }}
2.自顶向下方法实现
当然,不止那么一种算法。我们刚才是通过从后往前推的方式推导出结果的。
我们也可以换一种思路,从前往后退的方式进行推导。
对于空串的处理,我们和上面的处理是一样的
那么就对于不为空串的字符串就有
- 删除A第一个字符后,将A从2到尾的子串和B从1到尾的子串变成相同字符串
- 删除B第一个字符后,将A从1到尾的子串和B从2到尾的子串变成相同字符串
- 替换A或B第一个字符后,将A从2到尾的子串和B从2到尾的子串变成相同字符串
- 增加B第一个字符到A第一个字符后,将A从1到尾的子串和B从2到尾的子串变成相同字符串
- 增加A第一个字符到B第一个字符后,将A从2到尾的子串和B从1到尾的子串变成相同字符串
综上,我们可以得出如下结论
- 一步操作后,将A从1到尾的子串和B从2到尾的子串变成相同的字符串
- 一步操作后,将A从2到尾的子串和B从1到尾的子串变成相同的字符串
- 一步操作后,将A从2到尾的子串和B从2到尾的子串变成相同的字符串
那么我们就可以通过递归的方式得出结果
递归算法
public class Solution { public int minDistance(String word1, String word2) { // 初始化结果数组 return caculateDistance(word1,0,word1.length()-1, word2,0,word2.length() - 1); } public int caculateDistance(String word1,int begin1,int end1,String word2,int begin2,int end2){ if(begin1 > end1){ if(begin2 > end2){ return 0; } else{ return end2 - begin2 + 1; } } if(begin2 > end2){ if(begin1 > end1){ return 0; } else{ return end1 - begin1 + 1; } } //相等直接跳下一个 if(word1.charAt(begin1) == word2.charAt(begin2)){ return caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2); }else { //情况1 int t1 = caculateDistance(word1,begin1,end1,word2,begin2+1,end2); //情况2 int t2 = caculateDistance(word1,begin1+1,end1,word2,begin2,end2); //情况3 int t3 = caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2); return min(t1,t2,t3)+1; } } public int min(int t1,int t2,int t3){ int min = t1 > t2? t2 : t1; min = min > t3? t3 : min; return min; }}
上面的算法就是《编程之美》里面提供的算法。
不过我们可以发现,它在每一次调用的时候都会进行三次的自我递归调用。不难发现他的时间复杂度是O(3^n)。是一种很糟糕的算法。对于特别大的数据,耗时是非常大的。那么有没有改进措施呢?
该书也给出了一种改进的方法。
如下图是部分展开的递归调用
(注:图来源于《编程之美》)
可以发现,圈中的两个子问题被重复计算了。那么我们就像能不能减少计算的量。既然重复计算了,那么第二次计算就是没有必要的。所以我们最有效的方法就是把计算结果存储起来。当第二次调用的时候,我们直接用就好了。
下面是优化后的算法
带记忆的递归算法
public class Solution { //用来记录结果 int[][] result; public int minDistance(String word1, String word2) { // 初始化结果数组 result = new int[word1.length()+1][word2.length()+1]; for(int i = 0; i < word1.length()+1 ;i++){ for(int j = 0; j <word2.length()+1 ;j++){ result[i][j] = -1 ; } } return caculateDistance(word1,0,word1.length()-1, word2,0,word2.length() - 1); } public int caculateDistance(String word1,int begin1,int end1,String word2,int begin2,int end2){ if(result[begin1][begin2] > 0 ){ return result[begin1][begin2]; } if(begin1 > end1){ if(begin2 > end2){ result[begin1][begin2] = 0; return 0; } else{ result[begin1][begin2] = end2 - begin2 + 1; return result[begin1][begin2]; } } if(begin2 > end2){ if(begin1 > end1){ result[begin1][begin2] = 0; return 0; } else{ result[begin1][begin2] = end1 - begin1 + 1; return result[begin1][begin2]; } } if(word1.charAt(begin1) == word2.charAt(begin2)){ return caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2); }else { int t1 = caculateDistance(word1,begin1,end1,word2,begin2+1,end2); int t2 = caculateDistance(word1,begin1+1,end1,word2,begin2,end2); int t3 = caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2); result[begin1][begin2] = min(t1,t2,t3)+1; return result[begin1][begin2]; } } public int min(int t1,int t2,int t3){ int min = t1 > t2? t2 : t1; min = min > t3? t3 : min; return min; }}
- 算法笔记:编辑距离
- 编辑距离与编辑算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离及编辑距离算法
- 编辑距离算法-java
- 编辑距离算法
- 编辑距离算法实现
- 编辑距离算法
- [算法]计算编辑距离
- 字符串编辑距离算法
- LintCode-----17.带重复元素的子集
- mongodb实现类似sql中distinct的效果
- Android 下面调起键盘后的样式兼容性问题
- C++ Socket(六)
- OGG Configuring DDL Support
- 算法笔记:编辑距离
- 正则表达式获取url参数
- 用map对list进行分组
- JSP的3个编译指令、7个动作指令、9个内置对象
- 弹出ALV的几种方法(ALV POPUP)
- ios 内购
- 利用jquery的imgAreaSelect插件实现图片裁剪示例
- 文章标题
- Javaweb实现登录界面“记住我”功能