什么是最短编辑距离?

1,311 阅读4分钟

首先介绍一下编辑距离的概念:是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,如果它们的距离越大,说明它们越是不同。

之所以介绍求最短编辑距离的算法,是因为这个算法在很多场景会提到。

  • 用在自然语言处理中,例如拼写检查可以根据一个拼错的字和其他正确的字的编辑距离,判断哪一个(或哪几个)是比较可能的字。
  • DNA也可以视为用A、C、G和T组成的字符串,因此编辑距离也用在生物信息学中,判断二个DNA的类似程度。
  • 身为前端开发小白,我了解到这个算法是在学习React的diff算法的过成中,对于同一层次的element的比较时,有听到过这个概念。这里需要提醒一下,求最短编辑距离算法的时间复杂度是O(m*n),但是React对于同一层次element比较的算法的时间复杂度是O(n),这两者并不是完全等同的,之后有时间会单独写一篇关于React的diff算法中,同一层的element是如何比较的。

现在的场景是有两个单词 word1 和 word2,word1="happy",word2="baby",我们可以对word1进行三种操作,分别是:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

计算从word1 转换成 word2 所使用的最少操作数步数。这里只介绍我自己觉得最好理解的解法,就是使用动态规划求解。一般求解有多少种路径,有多少种解法等等问题,都会想到能不能使用动态规划求解。

先一步步解析动态规划的状态转移方程怎么列以及dp矩阵的每一项是什么含义。

 
      ""  b  a  b  y
    "" 0, 1, 2, 3, 4
    h  1, 1, 2, 3, 4
    a  2, 2, 1, 2, 3
    p  3, 3, 2, 2, 3
    p  4, 4, 3, 3, 3
    y  5, 5, 4, 4, 3

上面展示的二维矩阵就是从baby转化到happy中间步骤的dp矩阵。这个dp[i][j]表示从word1的第i(i从1开始)个字母变换到word2的第j(同样从1开始)个字母需要几步。先看矩阵第一行,dp[0][1]=1,这表示从b变换到""需要1步,也就是删除操作就可以。dp[1][1]=1表示从字符b转换成字符h,也是需要一步替换操作即可完成。dp[2][2]=dp[1][1]=1,dp矩阵中这两个元素之所以相等,是因为baby和happy的第二个元素相等,所以dp[2][2]=dp[1][1]。

分成两种情况,当word1[i]===word2[j]时,dp[i][j]=dp[i-1][j-1]。

但是当word[i]!==word[j]时,此时 dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1,这个表达式我详细解释一下。 dp[i-1][j-1]到dp[i][j]需要进行替换操作,dp[i-1][j]到d[i][j]需要进行删除操作,dp[i][j-1] 到d[i][j]需要进行添加操作。取这三个操作中的最小的一个,然后加上一步对应的操作。

列出了状态转移方程之后,代码就容易一些啦,下面是使用js实现的代码:

var minDistance = function(word1, word2) {
	let row = word1.length;
    let col = word2.length;
    //创建dp矩阵
    const dp = [];
    //为了创建二维矩阵,所用到的辅助的矩阵
    let tmp = new Array(col+1).fill(0);
    for(let i=0; i<row+1; i++){
    	dp[i] = [...tmp]; 
    }
    // dp矩阵的第一行
    for (let j = 1; j <= col; j++) dp[0][j] = dp[0][j - 1] + 1;
    // dp矩阵的第一列
    for (let i = 1; i <= row; i++) dp[i][0] = dp[i - 1][0] + 1;
    // dp矩阵的其它元素
    for (let i = 1; i <= row; i++) {
        for (let j = 1; j <= col; j++) {
            //当前字母相等时
            if (word1[i - 1] === word2[j - 1]){
            	dp[i][j] = dp[i - 1][j - 1];
            //如果当前字母不等
            }else{
             dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;            	
            } 
        }
    }
    return dp[row][col];  
}

这样就实现了最短编辑距离的计算。如果有哪里没有表达清晰的地方,欢迎随时交流。