1. 程式人生 > >動態規劃法(十一)編輯距離

動態規劃法(十一)編輯距離

[] 操作 算法 三方 cte main ring 高效 sam

編輯距離問題

??什麽是兩個字符串的編輯距離(edit distance)?給定字符串s1和s2,以及在s1上的如下操作:

  • 插入(Insert)一個字符
  • 移除(Remove)一個字符
  • 替換(Replace)一個字符

試問最小需要多少次這樣的操作才能使得s1轉換為s2?
??比如,單詞“cat”和“hat”,這樣的操作最少需要一次,只需要把“cat”中的“c”替換為“h”即可。單詞“recall”和“call”,這樣的操作最少需要兩次,只需要把“recall”中的“r”和“e”去掉即可。單詞“Sunday”和“Saturday”,這樣的操作最少需要3次,在“Sunday”的“S”和“u”中插入“a”和“t”,再把“n”替換成“r”即可。
??那麽,是否存在一種高效的算法,能夠快速、準確地計算出兩個字符串的編輯距離呢?

動態規劃算法

??我們使用動態規劃算法(Dynamic Programming)來計算出兩個字符串的編輯距離。
??我們從兩個字符串s1和s2的最末端向前遍歷來考慮。假設s1的長度為m,s2的長度為n,算法如下:

  1. 如果兩個字符串的最後一個字符一樣,那麽,我們就可以遞歸地計算長度為m-1和n-1的兩個字符串的情形;
  2. 如果兩個字符串的最後一個字符不一樣,那麽,進入以下三種情形:
    • 插入: 遞歸地計算長度為m和n-1的兩個字符串的情形,這是因為在s1中的末端插入了一個s2的最後一個字符,這樣s1和s2的末端字符一樣,就是1中情形;
    • 刪除: 遞歸地計算長度為m-1和n的兩個字符串的情形,這是在s1中的末端刪除了一個字符;
    • 替換: 遞歸地計算長度為m-1和n-1的兩個字符串的情形,這是因為把s1中末端字符替換成了s2的最後一個字符,這樣s1和s2的末端字符一樣,就是1中情形;

??這樣,我們就有了子結構問題。對於動態規劃算法,我們還需要一個初始化的過程,然後中間維護一張二維表即可。初始化的過程如下: 如果m為0,則至少需要操作n次,即在s1中逐個添加s2的字符,一共是n次;如果n為0,則至少需要操作m次,即把s1的字符逐個刪除即可,一共是m次。

Python實現

??利用DP算法解決兩個字符串的編輯距離的Python代碼如下:

# -*- coding: utf-8 -*-
# using Dynamic Programming to solve edit distance problem


# s1, s2 are two strings
def editDistDP(s1, s2):

   m, n = len(s1), len(s2)
   # Create a table to store results of subproblems
   dp = [[0 for _ in range(n+1)] for _ in range(m+1)]

   # using DP in bottom-up manner
   for i in range(m + 1):
       for j in range(n + 1):

           # If first string is empty, only option is to
           # isnert all characters of second string, thus the
           # min opration is j
           if i == 0:
               dp[i][j] = j

           # If second string is empty, only option is to
           # remove all characters of second string, thus the
           # min opration is i
           elif j == 0:
               dp[i][j] = i

           # If last characters are same, ignore last character
           # and recursive for remaining string
           elif s1[i-1] == s2[j-1]:
               dp[i][j] = dp[i-1][j-1]

           # If last character are different, consider all
           # possibilities and find minimum of inserting, removing, replacing
           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]


# Driver program
s1 = "sunday"
s2 = "saturday"
edit_distance = editDistDP(s1, s2)
print("The Edit Distance of ‘%s‘ and ‘%s‘ is %d."%(s1, s2, edit_distance))

輸出結果如下:

The Edit Distance of ‘sunday‘ and ‘saturday‘ is 3.

Java實現

??利用DP算法解決兩個字符串的編輯距離的Java代碼如下:

package DP_example;


// 計算兩個字符串的編輯距離(Edit Distance)
public class Edit_Distance {

   // 主函數
   public static void main(String[] args) {
       String str1 = "cat";//"Sunday";
       String str2 = "hat";//"Saturday";
       int edit_dist = edit_distance(str1, str2);
       System.out.println(String.format("The edit distance of ‘%s‘ and ‘%s‘ is %d.",
               str1, str2, edit_dist));
   }

   /*
   函數edit_distanc: 計算兩個字符串的編輯距離(Edit Distance)
   傳入參數:  兩個字符串str1和str2
   返回: 編輯距離
    */
   public static int edit_distance(String str1, String str2){

       // 字符串的長度
       int m = str1.length();
       int n = str2.length();

       // 初始化表格,用於維護子問題的解
       int[][] dp = new int[m+1][n+1];
       for(int i=0; i <= m; i++)
           for(int j=0; j <= n; j++)
               dp[i][j] = 0;

       // using DP 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
                * isnert all characters of second string, thus the
                * min opration is j
                */
               if(i == 0) { dp[i][j] = j;}

               /* If second string is empty, only option is to
                * remove all characters of second string, thus the
                * min opration is i
                */
               else if(j == 0){dp[i][j] = i;}

               /* If last characters are same, ignore last character
                * and recursive for remaining string
                */
               else if(str1.charAt(i-1) == str2.charAt(j-1)){
                   dp[i][j] = dp[i-1][j-1];
               }

               /*If last character are different, consider all
                *possibilities and find minimum of inserting, removing, replacing
                */
               else{
                   /*
                    * dp[i][j-1]: Insert
                    * dp[i-1][j]: Remove
                    * dp[i-1][j-1]: Replace
                    */
                   dp[i][j] = 1 + min(min(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]);
               }
           }
       }

       return dp[m][n];
   }

   public static int min(int i, int j){
       return (i <= j) ? i : j;
   }

}

輸出結果如下:

The edit distance of ‘cat‘ and ‘hat‘ is 1.

其它實現方式

??以上,我們用Python和Java以及動態規劃算法自己實現了編輯距離的計算。當然,我們也可以調用第三方模塊的方法,比如NTLK中的edit_distance()函數,示例代碼如下:

# 利用NLTK中的edit_distance計算兩個字符串的Edit Distance

from nltk.metrics import edit_distance

s1 = "recall"
s2 = "call"
t = edit_distance(s1, s2)
print("The Edit Distance of ‘%s‘ and ‘%s‘ is %d." % (s1, s2, t))

輸出結果如下:

The Edit Distance of ‘recall‘ and ‘call‘ is 2.

總結

??在本文中,我們對於兩個字符串的編輯距離的計算,只采用了插入、刪除、替換這三種操作,在實際中,可能還會有更多的操作,比如旋轉等。當然,這並不是重點,重點是我們需要了解解決這類問題的算法,即動態規劃算法。在後續的文章中,筆者會介紹編輯距離在文本處理中的應用。
??本次分享到此結束,歡迎大家交流~~

註意:本人現已開通微信公眾號: Python爬蟲與算法(微信號為:easy_web_scrape), 歡迎大家關註哦~~

動態規劃法(十一)編輯距離