概念
字符串的编辑距离,又称为Levenshtein距离,由俄罗斯的数学家Vladimir Levenshtein在1965年提出。是指利用字符操作,把字符串A转换成字符串B所需要的最少操作数。其中,字符操作包括:
- 删除一个字符 a) Insert a character
- 插入一个字符 b) Delete a character
- 修改一个字符 c) Replace a character
例如对于字符串"if"和"iff",可以通过插入一个'f'或者删除一个'f'来达到目的。
一般来说,两个字符串的编辑距离越小,则它们越相似。如果两个字符串相等,则它们的编辑距离(为了方便,本文后续出现的“距离”,如果没有特别说明,则默认为“编辑距离”)为0(不需要任何操作)。不难分析出,两个字符串的编辑距离肯定不超过它们的最大长度(可以通过先把短串的每一位都修改成长串对应位置的字符,然后插入长串中的剩下字符)。
问题描述
给定两个字符串A和B,求字符串A至少经过多少步字符操作变成字符串B。
问题分析
例如:
我们有两个字符串: kitten 和 sitting:
现在我们要将kitten转换成sitting
我们可以做如下的一些操作;
k i t t e n –> s i t t e n 将K替换成S
sitten –> sittin 将 e 替换成i
sittin –> sitting 添加g
在这里我们设置每经过一次编辑,也就是变化(插入,删除,替换)我们花费的代价都是1。
算法基本步骤:
(1)构造 行数为m+1 列数为 n+1 的矩阵 , 用来保存完成某个转换需要执行的操作的次数,将串s[1..n] 转换到 串t[1…m] 所需要执行的操作次数为matrix[n][m]的值;
(2)初始化matrix第一行为0到n,第一列为0到m。
Matrix[0][j]表示第1行第j-1列的值,这个值表示将串s[1…0]转换为t[1..j]所需要执行的操作的次数,很显然将一个空串转换为一个长度为j的串,只需要j次的add操作,所以matrix[0][j]的值应该是j,其他值以此类推。
(3)检查每个从1到n的s[i]字符;
(4)检查每个从1到m的s[i]字符;
(5)将串s和串t的每一个字符进行两两比较,如果相等,则让cost为0,如果不等,则让cost为1(这个cost后面会用到);
(6)a、如果我们可以在k个操作里面将s[1..i-1]转换为t[1..j],那么我们就可以将s[i]移除,然后再做这k个操作,所以总共需要k+1个操作。
b、如果我们可以在k个操作内将 s[1…i] 转换为 t[1…j-1] ,也就是说d[i,j-1]=k,那么我们就可以将 t[j] 加上s[1..i],这样总共就需要k+1个操作。
c、如果我们可以在k个步骤里面将 s[1…i-1] 转换为 t [1…j-1],那么我们就可以将s[i]转换为 t[j],使得满足s[1..i] == t[1..j],这样总共也需要k+1个操作。(这里加上cost,是因为如果s[i]刚好等于t[j],那么就不需要再做替换操作,即可满足,如果不等,则需要再做一次替换操作,那么就需要k+1次操作)
因为我们要取得最小操作的个数,所以我们最后还需要将这三种情况的操作个数进行比较,取最小值作为d[i,j]的值;
d、然后重复执行3,4,5,6,最后的结果就在d[n,m]中;
图解:
step 1:初始化如下矩阵
step 2:从源串的第一个字符(“j”)开始,从上至下与目标串进行对比
如果两个字符相等,则在从此位置的左,上,左上三个位置中取出最小的值;若不等,则在从此位置的左,上,左上三个位置中取出最小的值再加上1;
第一次,源串第一个字符“j” 与目标串的“j”对比,左,上,左上三个位置中取出最小的值0,因为两字符相等,所以加上0;接着,依次对比“j”→“e”,“j”→“r”,“j”→“r”,,“j”→“y” 到扫描完目标串。
step 3:遍历整个源串与目标串对比:
step 4:扫描完最后一列,则最后一个为最短编辑距离:
最小编辑距离算法
package com.math;
/**
* Created with IntelliJ IDEA.
* Description: 编辑距离算法 又称为 Levenshtein算法
* User: zhubo
* Date: 2017-11-02
* Time: 11:54
*/
public class Levenshtein {
public static void main(String[] args) {
String str1 = "beauty";
String str2 = "batyu";
System.out.println(getLevenShtein(str1,str2));
}
/**
* 获取最小编辑距离静态方法
* @param str1
* @param str2
* @return
*/
public static int getLevenShtein(String str1 , String str2){
int[][] array = initArray(str1,str2);
for(int i= 1 ; i<= str2.length();i++){//i 是竖排
for(int j = 1; j <= str1.length() ; j++){//j 是横排
setValue(array,i,j,str2.charAt(i-1) == str1.charAt(j-1));
}
}
printArray(array);
return array[str2.length()][str1.length()];
}
/**
*
* @param array
* @param i 代表纵向量
* @param j 代表横向量
* @param flag 代表字符是否相等
*/
private static void setValue(int[][] array,int i , int j, boolean flag){
int ss = array[i-1][j-1];
int dd = array[i-1][j] +1;
int pp = array[i][j-1] +1;
if(flag){
array[i][j] = getMin(ss,dd,pp);
}else{
array[i][j] = getMin(ss+1,dd,pp);
}
}
/**
* 求三个值中的最小值
* @param a
* @param b
* @param c
* @return
*/
private static int getMin(int a, int b , int c){
return Math.min(Math.min(a,b),c);
}
/**
* 打印矩阵
* @param array
*/
private static void printArray(int[][] array){
for(int i = 0; i < array.length;i++){
for(int j = 0;j<array[i].length ; j++){
System.out.print(array[i][j]+" ");
}
System.out.println();
}
}
/**
* 初始化矩阵
* @param str1 横坐标代表字符串
* @param str2 纵坐标代表字符串
* @return
*/
private static int[][] initArray(String str1,String str2){
int len1 = str1.length();
int len2 = str2.length();
int[][] array = new int[len2+1][len1+1];
for(int i = 0 ; i<= len1;i++){
array[0][i] = i;
}
for(int i = 0 ; i<= len2 ; i++){
array[i][0] = i;
}
return array;
}
}