最短编辑距离:
编辑距离是一种字符串之间相似程度的计算方法。按照Damerau给出的定义,即两个字符串之间的编辑距离等于使一个字符串变成另外一个字符串而进行的(1)插入、(2)删除、(3)替换或(4)相邻字符交换位置而进行操作的最少次数。编辑距离越短,两个字符串的相似度越高。
再看这些算法的过程当中,了解到一种思想,叫做动态规划,我对动态规划的理解也不是特别深刻,在编码的过去的几年少有接触过,对我而言是一种解决问题的全新思想,当时最长子序列,最短编辑距离都用到了这个动态规划的思想,这里记录一下这个作为问题,改天抽时间在好好研究一下这个动态规划。
动态规划:是一种在数学和计算机科学中使用的,用于求解包含重叠子问题的最优化问题的方法。其基本思想是,将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。
目前我对动态规划的理解就是讲一个复杂的问题,逐渐分解成一个一个的子问题,如果说这些子问题跟复杂的问题有相同的地方,那么就可以使用所谓的动态规划的方式,逐渐的分解复杂的问题,将复杂变一般,将一般的变简单,分解到最后就是一个很简单的问题,一眼就能够看出来。在了解编辑距离的过程中,看了很多的博主前辈们写的博客,才慢慢的对这个算法有了点儿认识。
整个算法的推导过程就是把一个复杂问题变简单,从简单问题一步步的归纳总结形成公式的一个过程。
假设有两个字符串 A和B
目的:判断最短距离
过程:
1:假设 A= “ABCD” ,B=“”,那么B跟A之间的编辑距离就是A的length ,4
2:假设B=“ABCD” ,A=‘’ ,那么B跟A之间的编辑距离就是B的length,4
3:假设A="a" ,B="B" ,那么B跟A之间的编辑距离就是1
4:假设A="AB" ,B="B",那么B跟A之间的编辑距离应该是1
先把一眼就能看到的情况都列出来,然后慢慢的寻找其中的规律。
从网上找一个矩阵图,来说明一下这个过程。 假设A= failina B = sailn,矩阵的每一个空白处就填写两个字符串之间的编辑距离。为空的情况很快就可以填写完毕
0 | f | a | i | l | i | n | g | |
0 | ||||||||
s | ||||||||
a | ||||||||
i | ||||||||
l | ||||||||
n |
在经过初始化之后,矩阵变成这样:
0 | f | a | i | l | i | n | g | |
0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
s | 1 | |||||||
a | 2 | |||||||
i | 3 | |||||||
l | 4 | |||||||
n | 5 |
(简单解释一下:0就表示空字符串,竖轴为B,横轴为A,
第一行表示 B为空时,(A字符串逐渐递增)之间的编辑距离
第二行表示 B为s 试,(A字符串逐渐递增)之间的编辑距离
第三行表示 B为sa时,(A字符串逐渐递增)之间的编辑距离)
整个推导过程,就是吧这张表填完的一个过程,最后那个空白坐标为(5,7)处填写的值就是最短的编辑距离。
现在尝试去填写一下A[1]B[1],这个地方的值,就是字符串A=f ,B=s之间的编辑距离,心算一下,太简单了等于1,这个地方的值就是1,
填写一下A[2]B[1],这个地方的值,就是字符串A=fa ,B=s之间的编辑距离,心算一下,太简单了等于2,这个地方的值就是2
矩阵为这样:
0 | f | a | i | l | i | n | g | |
0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
s | 1 | 1 | 2 | |||||
a | 2 | |||||||
i | 3 | |||||||
l | 4 | |||||||
n | 5 |
继续分析:A[2]B[1],这个地方的值,就是A=fa ,B=s之间的编辑距离,如果我们一步一步的来走的话,一共有几种走法,
一种走法:B ,先删除 ,变为空,即B[1]先走到B[0],一步,B[0]到A[2],又是两步 ,一共三步
一种走法:B,现替换为f,即变为f,即B【1】先走到了B【1】A【1】,然后后面加a,一共两步
一种走法:A ,先删除A,即变为f,即A【2】先走到了A【1】,然后替换为s,从A【1】走到A【1】B【1】,一共两步。
继续分析:
位置A【2】B【1】这个位置的值跟那几个值有关系呢,
我们可以知道他的上一步的时候已经花了多少步骤已经可以替换完成了。现在只需要最后一步就可以算出来一共需要多少步就能得出其最短距离。
位置A【2】B【1】可以通过上,左,左上,三个位置走过去,看一看两个字符串完全匹配,都需要几步,
从左边过来需要多走一步,左+1 =2
从上面过来,需要多走一步,上+1=3
从左上过来,需要多走一步,左上+1=2,那么最终的编辑距离最短就是从以上的步骤中选择一个需要步数最少的。
这个说法可能不是很严谨,因为从左上过来,虽然步数+1,不一定编辑距离就要+1,最后一步的时候如果要比较的两个字符正好想等,那么虽然我又往前走了一步,但是我没有
做任何的编辑操作,所以编辑距离不用+1.
,假设 A.length = 10 ,B.length=9,所以最终的编辑距离有MIN({A(10-1),B(9-1)的编辑距离},{A(10-1),B(9)的编辑距离+1},{A(10),B(9-1)的编辑距离}+1)的最少值决定的,以此类对,每一个问题都是类似的,
所以可以使用递归的方式进行计算。
/**
* 字符串编辑距离
*
* 这是一种字符串之间相似度计算的方法。 给定字符串S、T,将S转换T所需要的插入、删除、替代操作的数量叫做S到T的编辑路径。 其中最短的路径叫做编辑距离。
*
* 这里使用了一种动态规划的思想求编辑距离。
*
*
*/
public class RecursiveDistance {
/** 字符串X */
private String strX = "";
/** 字符串Y */
private String strY = "";
/** 字符串X的字符数组 */
private char[] charArrayX = null;
/** 字符串Y的字符数组 */
private char[] charArrayY = null;
public RecursiveDistance(String sa, String sb) {
this.strX = sa;
this.strY = sb;
}
/**
* 得到编辑距离
*
* @return 编辑距离
*/
public int getDistance() {
charArrayX = strX.toCharArray();
charArrayY = strY.toCharArray();
return editDistance(charArrayX.length - 1, charArrayY.length - 1);
}
/**
* 动态规划解决编辑距离
*
* editDistance(i,j)表示字符串X中[0.... i]的子串 Xi 到字符串Y中[0....j]的子串Y1的编辑距离。
*
* @param i 字符串X第i个字符
* @param j 字符串Y第j个字符
* @return 字符串X(0...i)与字符串Y(0...j)的编辑距离
*/
private int editDistance(int i, int j) {
// 如果两者都为0,则最短编辑距离为0
if (i == 0 && j == 0) {
return isModify(i, j);
} else if (i == 0 || j == 0) {
if (j > 0) {
// System.out.println("edit["+i+","+j+"]=edit["+i+","+(j-1)+"]+1");
if (isModify(i, j) == 0){
return j;
}
return editDistance(i, j - 1) + 1;
} else {
// System.out.println("edit["+i+","+j+"]=edit["+(i-1)+","+j+"]+1");
if (isModify(i, j) == 0){
return i;
}
return editDistance(i - 1, j) + 1;
}
} else {
int ccc = minDistance(editDistance(i - 1, j) + 1, editDistance(i, j - 1) + 1, editDistance(i - 1, j - 1) + isModify(i, j));
return ccc;
}
}
/**
* 求最小值
*
* @param disa 编辑距离a
* @param disb 编辑距离b
* @param disc 编辑距离c
*/
private int minDistance(int disa, int disb, int disc) {
int dismin = Integer.MAX_VALUE;
if (dismin > disa)
dismin = disa;
if (dismin > disb)
dismin = disb;
if (dismin > disc)
dismin = disc;
return dismin;
}
/**
* 单字符间是否替换
*
* isModify(i,j)表示X中第i个字符x(i)转换到Y中第j个字符y(j)所需要的操作次数。 如果x(i)==y(j),则不需要任何操作isModify(i, j)=0;
* 否则,需要替换操作,isModify(i, j)=1。
*
* @param i 字符串X第i个字符
* @param j 字符串Y第j个字符
* @return 需要替换,返回1;否则,返回0
*/
private int isModify(int i, int j) {
if (charArrayX[i] == charArrayY[j])
return 0;
else
return 1;
}
/**
* 测试
*
* @param args
*/
public static void main(String[] args) {
System.out.println("编辑距离是:" + new RecursiveDistance("d","abd").getDistance());
}
}