編輯距離算法


定義

給定兩個字符串s1和s2,兩者的編輯距離定義為將s1轉換為s2的最小編輯操作數(等價於將s2轉換為s1的最小編輯操作數)。

編輯操作有3種:插入一個字符、刪除一個字符、替換一個字符。

例如:cat和cbt的編輯距離是1(將a替換為b);cat到ca的編輯距離是1(刪除t);ct到cat的編輯距離是1(插入a);xcat到caty的編輯距離是2(刪除x,插入y)。

求解方法

知道了編輯距離的定義,那么如何求最小編輯距離呢?這里用到了動態規划的思想。

用例子來說明,假如我們要求解 jary和jerry的最小編輯距離,那么首先要創建如下矩陣:

    j a r y
   1  2  4
j 1        
e        
r        
r        
y        

這個矩陣什么意思呢?第一行是字符串jary,第一列是字符串jerry,每個標有數字的單元格代表了兩個字符串的子串的最小編輯距離。第二行第二列的0代表兩個字符串都取空子串的時候,編輯距離就是0(子串相等);第二行第三列的1代表當jerry的子串取空,jary的子串取j時,兩個子串的最小編輯距離是1(給jerry的子串插入j)。其他的依次類推,可以很容易得出當前矩陣中的第二行和第二列的數字。

而我們最終要求的兩個字符串的最小編輯距離對應的就是矩陣右下角的那個單元格,它代表當jary子串取jary,jerry子串取jerry時,兩個子串的最小編輯距離,也就是兩個字符串的最小編輯距離。

 

這里我先直接說怎么求,然后再解釋原理。看下面的矩陣,我在中心空白的位置標上了從x1到x20,這里x后面的數字代表我們求解時的順序。

    j a r y
   1  2  4
j 1 x1 x6  x11  x16 
e x2 x7  x12  x17 
r x3  x8  x13  x18 
r x4  x9  x14  x19 
y x5  x10  x15  x20 

如果按順序求解的話,那么在求解每一個值的時候,它的左、上、左上三個位置的單元格值肯定都是已知的,將這三個單元格里的值分別定義為left、top、leftTop,則要求解的單元格的值v為:

cost=若單元格橫向對應的字符和縱向對應的字符相等則為0否則為1

min(left+1,top+1,leftTop+cost)

 

按照求解方法求解后的矩陣:

 

    j a r y
   1  2  4
j 1  0
e
r
r
y 2

取右下角的值,因此jary和jerry的編輯距離是2(替換a為e,插入一個r)。

求解原理 

 通過上面的介紹我們可以用矩陣求兩個字符串的最小編輯距離了,但是這么做的原理是什么呢?其實很簡單,當我們要求字符串s[1...i]到t[1...j]的編輯距離的時候:

  1. 如果我們知道可以在k1個操作內將s[1...i-1]轉換為t[1...j],那么用k1+1次操作一定能將s[1...i]轉化為t[1...j],因為只需要先做一次移除操作移除s[i]將s[1...i]轉化為s[1...i-1],然后再做k1個操作就可以轉換為t[1...j]。
  2. 如果我們知道可以在k2個操作內將s[1...i]轉換為t[1...j-1],那么用k2+1次操作一定能將s[1...i]轉化為t[1...j],因為我們可以先用k2次操作將s[1...i]轉化為t[1...j-1],然后再執行一次插入操作在末尾插入t[j]即可將s[1...i]轉化為t[1...j]
  3. 如果我們知道可以在k3個操作內將s[1...i-1]轉化為t[1...j-1],那么如果s[i]==t[j],則將s[1...i]轉換為t[1...j]只需要k3次操作,如果s[i]!=t[j],則需要做一次替換操作將s[i]替換為t[j],這種情況下需要k3+1次操作。

而上面我們討論的3中情況下的k1、k2、k3就對應着矩陣里一個單元格的左、上、左上的單元格里的值。

上述結論可以表述為如下公式:

 

 

 

實現代碼

明白了原理之后,寫代碼很簡單,就是用代碼模擬計算矩陣的過程(java實現),時間復雜度是O(|s1|*|s2|)  (|s1|、|s2|分別代表字符串s1和s2的長度):

package common;

import org.junit.Assert;

public class LevenshteinDistance {

        public static int getDistance(String src, String des) {
            int[][] m=new int[des.length()+1][];
            
            for (int i = 0; i < m.length; i++) {
                m[i]=new int[src.length()+1];
            }
            
            for(int i=0;i<src.length()+1;i++){
                m[0][i]=i;
            }
            
            for(int i=0;i<des.length()+1;i++){
                m[i][0]=i;
            }
            
            for(int i=1;i<des.length()+1;i++){
                for (int j = 1; j < src.length()+1; j++) {
                    int rcost=des.charAt(i-1)==src.charAt(j-1)?0:1;
                    m[i][j]=Math.min(Math.min(m[i-1][j]+1, m[i-1][j-1]+rcost), m[i][j-1]+1);
                }
            }
            
            return m[des.length()][src.length()];
        }
        
        public static void main(String[] args) {
            Assert.assertEquals(getDistance("cat", "dog"), 3); //replace
            Assert.assertEquals(getDistance("cat", "cbt"), 1); //replace
    
    
            Assert.assertEquals(getDistance("cat", "ca"), 1); //delete
            Assert.assertEquals(getDistance("catx", "cat"), 1); //delete
    
            Assert.assertEquals(getDistance("ct", "cat"), 1); //insert
    
            Assert.assertEquals(getDistance("xcat", "caty"), 2); //delete and insert
            
            Assert.assertEquals(getDistance("fast", "cats"), 3);
            Assert.assertEquals(getDistance("cats", "fast"), 3);
            
            Assert.assertEquals(getDistance("kitten", "sitting"), 3);
            Assert.assertEquals(getDistance("sitting", "kitten"), 3);
            
            Assert.assertEquals(getDistance("jary", "jerry"), 2);
            Assert.assertEquals(getDistance("jerry", "jary"), 2);
            
        }

}

總結

要學會編輯距離算法主要就是掌握兩點,第一是要會通過矩陣手算兩個字符串的編輯距離,第二就是明白為什么可以這么計算。掌握了這兩點寫程序就很簡單了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM