編輯距離算法和Levenshtein距離算法

前言

最近在研究diff工具的實現,已經寫了一個簡單的demo,不過目前這個demo只是把Levenshtein距離算法的結果用Qt可視化了出來而已,還沒有實用價值,界面如下:

qdiff

各種diff工具的核心基本都是編輯距離算法,網上許多文章把編輯距離算法等同於Levenshtein距離算法,但實際上Levenshtein距離算法只是各種編輯距離算法其中之一。各種編輯距離算法會使用不同的編輯操作種類,例如最常見的,Levenshtein距離算法使用複製、插入、刪除和替換4種操作,而LCS距離算法使用複製、插入和刪除3種操作。

編輯距離算法

在我看到的關於編輯距離算法的各種表述中,最嚴謹和容易理解的是在《算法導論》中的思考題15-5,詳細的內容可參考該書,精簡後的表述爲:

給一個源文本串x[1…m]和一個目標文本串y[1…n],以及一組可用的編輯操作集合。編輯操作的結果保存在z,z是一個初始爲空的足夠大的緩衝區,要求編輯結束時z與y相同。我們維護兩個下標i和j,分別指向x種位置和z中位置,變換操作允許改變z的內容和這兩個下標。初始時,i=j=1。在編輯過程中應處理x的所有字符。可用的編輯操作有6種:

  1. 複製:從x複製一個字符到z,即進行賦值z[j]=x[i],然後i和j都自增1。此操作處理了x[i]。
  2. 替換:將x中一個字符替換爲另一個字符c,z[j]=c,然後i和j都自增1。此操作處理了x[i]。
  3. 刪除:刪除x中一個字符,即將i增1,j不變。此操作處理了x[i]。
  4. 插入:將字符c插入z中,z[j]=c,將j增1,i不變。此操作未處理x中字符。
  5. 旋轉:將x中下兩個字符複製到z中,但交換順序,z[j]=x[i+1]且z[j+1]=x[i],將i和j都自增2。此操作處理了x[i]和x[i+1]。
  6. 終止:刪除x中剩餘字符,令i=m+1。此操作處理了x中所有尚未處理的字符。如果執行此操作,則編輯過程結束。
    每個編輯操作都有相應的成本,具體的代價依賴於特定的應用,在此假定每個操作的成本是一個已知的常量。若源文本串、目標文本串、可用編輯操作及其代價已知,求一組可行的編輯操作序列a[1…k],其操作成本和S爲最優(小)。

算法分析

現分析如下,爲簡單起見,就拿Levenshtein距離算法來分析,Levenshtein距離算法支持複製、替換、刪除和插入4種編輯操作,若其成本分別爲0、1、1、1,那麼最後的成本和就是替換、刪除和插入的操作次數。

記S(m,n)爲將x[1…m]編輯爲y[1…n]的最優成本和。最優編輯操作序列a[1…k]的每個操作一定爲4種操作其中的一種,操作序列最後一個操作爲a[k]:

  1. 若a[k]爲複製,則要求x[m]=y[n],除去這次操作,之前操作的用於將x[1…m-1]編輯爲y[1…n-1]。那麼總的成本和S(m,n)’=S(m-1,n-1)+0。

  2. 若a[k]爲替換,則要求x[m]!=y[n],除去這次操作,之前操作的用於將x[1…m-1]編輯爲y[1…n-1]。總成本和爲S(m,n)’’=S(m-1,n-1)+1。

  3. 若a[k]爲刪除,除去這次操作,之前的操作用於將x[1…m-1]編輯爲y[1…n]。總成本和爲S(m,n)’’’=S(m-1,n)+1。

  4. 若a[k]爲插入,除去這次操作,之前的操作用於將x[1…n]編輯爲y[1…n-1]。總成本和爲S(m,n)’’’’=S(m,n-1)+1。

有了對a[k]的所有可能情況的分析,我們就可以得到S(m,n)=min((m,n)’,S,S(m,n)’’’,S(m,n)’’’’)或S(m,n)=min((m,n)’’,S,S(m,n)’’’,S(m,n)’’’’),適用前者還是後者取決於x[m]是否等於y[n]。這是一個遞歸式,S(m,n)依賴於S(m-1,n-1)、S(m-1,n)、S(m,n-1),因此還需要一個終止條件,這個終止條件是m=0或n=0,此時有S(0,n)=n和S(m,0)=m。

此處的分析使用了遞歸表述,爲了避免重複計算,需要轉換爲使用動態規劃,這個轉換很簡單,不再表述。

後記

編輯距離算法很慢,如果字符串的基本單位是字符,時間複雜度是O(mn)。如果字符串的基本單位是行,那還要乘上比較兩行字符串的複雜度,這個複雜度可能是另一個O(n),也可能是一個比較大的整數(取決於對每行長度的限制是多少)。因此想要寫一個實用的diff工具的話,要尋找其他優化過的算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章