最短编辑距离

场景

在搜索引擎项目中,我用到了最短编辑距离算法,用于对用户输入的查询进行纠错,从而优化查询结果。比如说,我们在输入英文单词的时候,由于疏忽或者记忆不准确,会有拼写错误的情况。以单词beautiful 为例,假设我们在搜索引擎中输入beauitful(我故意拼错了),看看会发生什么。
 
如下图所示,虽然我把这个单词拼错了,但是查询结果提示“including results for beautiful”,也就是说,它似乎知道我的查询输入拼写错误,并根据某种算法,给我推荐了一个与之最近似的单词(大概率就是本应正确拼写的单词)。在这里,就用到了最短编辑距离算法。这个场景,也称为模糊搜索。

 最短编辑距离

什么是最短编辑距离呢?假定有两个字符串s1和s2,允许对字符串进行以下三种操作:

1. 插入一个字符

2. 删除一个字符

3. 替换一个字符

将字符串s1转换成字符串s2的最少操作次数就是字符串s1和字符串s2之间的最短编辑距离。两个字符串的最短编辑距离越短,意味着两个字符串越相似。
 
例1 :s1 = "geek",s2 = "gesek"
我们只需要在s1中插入一个字符,就可以把s1转换为s2,因此,这两个字符串的最短编辑距离就是1
 
例2:s1 = "cat",s2 = "cut"
我们只需要在s1中替换一个字符,就可以把s1转换为s2,因此,这两个字符串的最短编辑距离就是1
 
例3:s1 = "sunday",s2 = "saturday"
由于第1个字符和最后3个字符是一样的,因此,我们只要考虑“un”和"atur"即可。首先,把'n'替换成'r',再插入'a'、't',因此最短编辑距离是3
 
以上面例3进行说明,我们从字符串的最后一位开始,从右向左进行比较,由于最后一位都是'y',因此,不需要任何操作,也就是说,两者的最短编辑距离等价于"sunda"和"saturda"的最短编辑距离,即d("sunday", "saturday") = d("sunda", "saturda")。因此,如果在比较的过程中遇到了相同的字符,那么二者的最短编辑距离就等价于除了这个字符之外,剩余字符的最短编辑距离,即d(i, j) = d(i-1, j-1)。
 
如果比较的字符不一致,比方说,已经比较到了"sun"和"satur",根据允许的操作,我们有以下3种操作:
(1)插入:在s1末尾插入一个字符'r'(即"sunr"),由于此时末尾字符都是'r',因此就变成了比较"sun"和"satu"的编辑距离,即d("sun", "satur") = d("sun", "satu") + 1,也可以写成d(i, j) = d(i, j-1) + 1。+1 表示当前进行了一次字符操作。
(2)删除:删除s1的最后一个字符,并考察s1剩下的部分与s2的距离。即d("sun", "satur") = d("su", "satur") + 1,也可以写成d(i, j) = d(i-1, j) + 1。
(3)替换:把s1的最后一个字符替换为s2的最后一个字符,即变成了"sur",因此即d("sun", "satur") = d("su", "satu") + 1,也可以写成d(i, j) = d(i-1, j-1) + 1。
 
基于上述分析,我们就可以很快写出递归的代码。如下:
static int min(int x,int y,int z)
    {
        if (x<=y && x<=z) return x;
        if (y<=x && y<=z) return y;
        else return z;
    }
 
static int editDist(String str1 , String str2 , int m ,int n)
    {
        // If first string is empty, the only option is to
    // insert all characters of second string into first
    if (m == 0) return n;
       
    // If second string is empty, the only option is to
    // remove all characters of first string
    if (n == 0) return m;
       
    // If last characters of two strings are same, nothing
    // much to do. Ignore last characters and get count for
    // remaining strings.
    if (str1.charAt(m-1) == str2.charAt(n-1))
        return editDist(str1, str2, m-1, n-1);
       
    // If last characters are not same, consider all three
    // operations on last character of first string, recursively
    // compute minimum cost for all three operations and take
    // minimum of three values.
    return 1 + min ( editDist(str1,  str2, m, n-1),    // Insert
                     editDist(str1,  str2, m-1, n),   // Remove
                     editDist(str1,  str2, m-1, n-1) // Replace                     
                   );
    }

但是我们都知道递归会存在大量的重复计算,因此,显然不是最优解。在这里,我们可以利用动态规划的思想来进行优化。

 
假设dp[i][j]表示s1[i]与s2[j]的最短编辑距离,根据之前的分析,可以写出如下代码:
static int editDistDP(String str1, String str2, int m, int n)
    {
        // Create a table to store results of subproblems
        int dp[][] = new int[m+1][n+1];
       
        // Fill d[][] in bottom up manner
        for (int i=0; i<=m; i++)
        {
            for (int j=0; j<=n; j++)
            {
                // If first string is empty, only option is to
                // insert all characters of second string
                if (i==0)
                    dp[i][j] = j;  // Min. operations = j
       
                // If second string is empty, only option is to
                // remove all characters of second string
                else if (j==0)
                    dp[i][j] = i; // Min. operations = i
       
                // If last characters are same, ignore last char
                // and recur for remaining string
                else if (str1.charAt(i-1) == str2.charAt(j-1))
                    dp[i][j] = dp[i-1][j-1];
       
                // If the last character is different, consider all
                // possibilities and find the minimum
                else
                    dp[i][j] = 1 + min(dp[i][j-1],  // Insert
                                       dp[i-1][j],  // Remove
                                       dp[i-1][j-1]); // Replace
            }
        }
   
        return dp[m][n];
    }

时间复杂度:O(m*n)

空间复杂度:O(m*n)
 
参考:
 
 
 
posted @ 2019-09-13 22:22  kkbill  阅读(1700)  评论(0编辑  收藏  举报