[最详细]最短编辑距离的原理解释与Java代码实现(Minimum Edit Distance,Levenshtein距离,代码)

一、问题背景

博主最近在准备2020年的软件工程编程领域的春招,其中华为提供了博主上机考试的机会,因此博主参考了2020年3月4日的华为校园春招的真题,其中真题的最后一题便是最短编辑距离的改编题,但博主我不清楚它的具体实现方法,于是对此在国内外网站上进行了详细的了解,下面为最短编辑距离的原理解释与Java代码实现。

二、最短编辑距离(Minimum Edit Distance)

1. 最短编辑距离的背景介绍

最短编辑距离(Minimum Edit Distance),也被称为Levenshtein距离,是一种计算两个字符串间的差异程度的字符串度量(string metric)。我们可以认为Levenshtein距离就是从一个字符串修改到另一个字符串时,其中编辑单个字符(比如修改、插入、删除)所需要的最少次数。俄罗斯科学家Vladimir Levenshtein于1965年提如何编程实现这一算法呢?许多人试图用矩阵来解释,但实际上矩阵是最终可视化的工具,配合理解“为什么”比较方便,但从矩阵却比较难想到“怎么做”。

我们试图找到“从字符串AA修改到字符串BB”这一问题的子解结构。当然反过来说“从字符串BB修改到字符串AA”和它是同一个问题,因为从AA中删掉一个字符来匹配BB,就相当于在BB中插入一个字符来匹配AA,这两个操作是可以互相转化的。

假设字符序列A[1…i]A[1…i]、B[1…j]B[1…j]分别是字符串AA、BB的前ii、jj个字符构成的子串,我们得到一个子问题是“从字符串A[1…i]A[1…i]修改到字符串B[1…j]B[1…j]”:出了这一概念。

2. 最短编辑距离的简单例子

给定两个单词word1和word2,找到最小的修改次数,把word1转换成word2,每一次操作记为一次,允许在一个单词上进行如下3种操作:

  1. 插入一个字符
  2. 删除一个字符
  3. 替换一个字符

那么从字符串"ssitten"修改为字符串"sitting"最少需3次单字符编辑操作,其最少的字符编辑操作如下:

单词开头删除1个s
e换成i
单词末尾插入1个g
ssitten
sitten
sittin
sitting

因此字符串"ssitten"和字符串"sitting"的最短编辑距离为3。

3.最短编辑距离的详细视频讲解

下方最短编辑距离的详细讲解视频的原作者是Youtube频道的Back To Back SWE,而该视频链接由Bilibili提供,因此你可以直接用中国内网打开并观看。但在CSDN观看B站该视频似乎无法显示并拖动视频的进度条,你也可以直接访问B站该视频网址:【LeetCode算法题详解】编辑距离(动态规划)。

该视频即使在Youtube中也没有自动生成的英文字幕,但视频中演讲者的英文口头表达清晰且简单,英语能力合格的大学生都能听懂。观看视频的主要内容是视频演讲者如何计算最短编辑距离的二维数组的各个元素数值,因此部分英文表达听不懂也不用担心。

强烈建议你先观看下方视频再继续浏览本文,否则你将很难理解下文的原理解释和Java代码实现。

最短编辑距离原理的详细视频讲解

4.最短编辑距离原理

如何编程实现这一算法呢?许多人试图用矩阵来解释,但实际上矩阵是最终可视化的工具,配合理解“为什么”比较方便,但从矩阵却比较难想到“怎么做”。

我们试图找到“从字符串A修改到字符串B”这一问题的子解结构。当然反过来说“从字符串B修改到字符串A”和它是同一个问题,因为从A中删掉一个字符来匹配B,就相当于在B中插入一个字符来匹配A,这两个操作是可以互相转化的。

假设字符序列A[1…i]、B[1…j]分别是字符串A、B的前i、j个字符构成的子串,我们得到一个子问题是“从字符串A[1…i]修改到字符串B[1…j]”:

  1. 插入操作:
    当将A[1…i]修改成B[1…j−1]需要操作数为op1,那么我插入一个字符A[i′]=B[i]到A[i]和A[i+1]之间,用以匹配B[j],于是A[1…i]修改到B[1…j]所需操作数为op1+1。
  2. 删除操作:
    当将A[1…i−1]修改成B[1…j]需要操作数为op2,那么我删掉字符A[i]也可以op2+1的操作数使两个子字符串匹配:
  3. 修改操作:
    如果A[1…i−1]修改成B[1…j−1]所需操作数为op3的话,我将字符A[i]替换成A[i′]=B[j],就可以op3+1的操作数完成:
    但如果此时字符A[i]==B[j]的话,则不需要进行修改操作,操作数仍为op3。

综上所述,我们将字符串A[1…i]修改成字符串B[1…j]所需操作为:

  1. 如果末尾字符相等,那么:op[i][j]=op[i-1][j-1]
  2. 如果末尾字符不相等:min{op[i-1][j], op[i][j-1], op[i-1][j-1]} +1

5.最短编辑距离的Java代码实现

public class MinimumEditDistance {
    public static void main(String[] args) {
        //字符串word1和word2分别是要进行对比的两个字符串
        String word1="legend";
        String word2="laland";
        //在本类中调用本类的静态方法minimumEditDistance(...)故不需在方法前加类名或创建类对象
        //输出字符串word1和word2互相转换的最小编辑距离
        System.out.println(minimumEditDistance(word1,word2));
    }
    //注意此方法是静态方法,这样在本类的main方法中可直接用本方法名调用本方法,而不需要创建对象再调用此方法
    //本方法的形式参数word1和word2分别是要进行对比的两个字符串,最后返回的是int型的两个字符串互相转换的最小编辑距离
    public static int minimumEditDistance(String word1,String word2){
        //新建一个二维数组,用于存取两字符串的各个子字符串之间的最短编辑距离
        //其中新建的二维数组的行列都+1是因为最短编辑距离的矩阵的子字符串从空串""开始,把空串""考虑进去,因此行列都+1
        int[][] minMatrix=new int[word1.length()+1][word2.length()+1];
        //minMatrix[i][j]表示:字符串word1的第0到第i-1个字符构成的子字符串 与 字符串word2的第0到第j-1个字符构成的子字符串 互相转换的最小编辑距离
        //例如word1="legend"而word2="laland",那么minMatrix[2][3]的结果是2,其表示 字符串word1的第0到第1个字符构成的子字符串"le" 与 字符串word2的第0到第2个字符构成的子字符串"lal" 互相转换的最小编辑距离为2

        //当word2的子字符串是空串""时,而当word1的子字符串分别是:空串""、第0个字符、第0到第1个字符、第0到第2个字符...第0到第i-1个字符构成的子字符串时,
        //minMatrix[i][0]表示该两个子字符串互相转换的最小编辑距离
        for (int i = 0; i < word1.length(); i++) {
            minMatrix[i][0]=i;
        }
        //当word1的子字符串是空串""时,而当word2的子字符串分别是:空串""、第0个字符、第0到第1个字符、第0到第2个字符...第0到第j-1个字符构成的子字符串时,
        //minMatrix[0][j]表示该两个子字符串互相转换的最小编辑距离
        for (int j = 0; j < word2.length(); j++) {
            minMatrix[0][j]=j;
        }
        //建一个二层循环嵌套,分别比较并计算word1和word2的子字符串都不是空串""时互相转换的最小编辑距离
        for (int i = 1; i < word1.length()+1; i++) {
            for (int j = 1; j < word2.length()+1; j++) {
                //word1.charAt(i-1)方法指字符串word1的第i个字符,而第i个字符的数组下标是i-1
                //若某两个子字符串的最后一个字符相同,那么此时此两个子字符串的最短编辑距离等价于:没有最后一个相同字符的两个子字符串互相转换的最短编辑距离
                //例如"lalan"和"legen"的最后一个字符都是"n",则 "lalan"和"legen"的最小编辑距离 等于 "lala"和"lege"的最小编辑距离
                if (word1.charAt(i-1)==word2.charAt(j-1)){
                    minMatrix[i][j]=minMatrix[i-1][j-1];
                }
                //若某两个子字符串的最后一个字符不相同,那么此时此两个子字符串的最短编辑距离等价于:minMatrix[i-1][j]和minMatrix[i][j-1]和minMatrix[i-1][j-1]之间的最小值,再+1
                //最后+1是因为最后一个字符不相同。若是minMatrix[i-1][j-1]+1,则是某两个子字符串的最后两个不同字符进行1次替换字符操作,从而让该两字符相同;
                //若是minMatrix[i-1][j]+1或minMatrix[i][j-1]+1,则是某两个子字符串的两个不同字符进行1次插入/删除字符操作;
                else{
                    minMatrix[i][j]=(Math.min((Math.min(minMatrix[i-1][j],minMatrix[i][j-1])),minMatrix[i-1][j-1]))+1;
                }
            }
        }
        
        //最后返回的是int型的两个字符串互相转换的最小编辑距离,该值是行列分别是word1和word2的长度的二维数组minMatrix的元素值
        return minMatrix[word1.length()][word2.length()];
    }
}

本文参考文献:
[1]编辑距离的原理与java实现
[2]Minimum Edit Distance - Explained ! - Stanford University
[3]牛客试题—编辑距离(Edit Distance)

你可能感兴趣的:(面试算法)