Levenshtein演算法,用於計算兩個字串之間的Levenshtein距離。而Levenshtein距離又稱為編輯距離,是指兩個字串之間,由一個轉換成另一個所需的最少編輯操作次數。許可的編輯操作包括將一個字元替換成另一個字元,插入一個字元,刪除一個字元。
概述
Levenshtein距離用來描述兩個字串之間的差異。我在一個網路爬蟲程式裡面使用這個演算法來比較兩個網頁之間的版本,如果網頁的內容有足夠多的變動,我便將它更新到我的資料庫。
說明
原來的演算法是建立一個大小為StrLen1*StrLen2的矩陣。如果所有字串加起來是1000個字元那麼長的話,那麼這個矩陣就會是1M;如果字串是10000個字元,那麼矩陣就是100M。如果元素都是整數(這裡是指數字,Int32)的話,那麼矩陣就會是4*100M == 400MB這麼大,唉……
現在的演算法版本只使用2*StrLen個元素,這使得後面給出的例子成為2*10,000*4 = 80 KB。其結果是,不但記憶體佔用更少,而且速度也變快了!因為這使得記憶體分配只需要很少的時間來完成。當兩個字串的長度都是1k左右時,新演算法的效率是舊演算法的兩倍!
示例
原來的版本將會建立一個矩陣[6+1, 5+1],而我的新演算法將會建立兩個向量[6+1](黃色元素)。在這兩個演算法版本中,字串的順序是無關緊要、無所謂的,也就是說,它也可以是矩陣[5+1, 6+1]和兩個向量[5+1]。
新的演算法
步驟
步驟 | 說明 |
---|---|
1 | 設定n為字串s的長度。(“GUMBO”) 設定m為字串t的長度。(“GAMBOL”) 如果n等於0,返回m並退出。 如果m等於0,返回n並退出。 構造兩個向量v0[m+1] 和v1[m+1],串聯0..m之間所有的元素。 |
2 | 初始化 v0 to 0..m。 |
3 | 檢查 s (i from 1 to n) 中的每個字元。 |
4 | 檢查 t (j from 1 to m) 中的每個字元 |
5 | 如果 s[i] 等於 t[j],則編輯代價為 0; 如果 s[i] 不等於 t[j],則編輯代價為1。 |
6 | 設定單元v1[j]為下面的最小值之一: a、緊鄰該單元上方+1:v1[j-1] + 1 b、緊鄰該單元左側+1:v0[j] + 1 c、該單元對角線上方和左側+cost:v0[j-1] + cost |
7 | 在完成迭代 (3, 4, 5, 6) 之後,v1[m]便是編輯距離的值。 |
本小節將演示如何計算”GUMBO”和”GAMBOL”兩個字串的Levenshtein距離。
步驟1、2
v0 | v1 | |||||
G | U | M | B | O | ||
0 | 1 | 2 | 3 | 4 | 5 | |
G | 1 | |||||
A | 2 | |||||
M | 3 | |||||
B | 4 | |||||
O | 5 | |||||
L | 6 |
步驟3-6,當 i = 1
v0 | v1 | |||||
G | U | M | B | O | ||
0 | 1 | 2 | 3 | 4 | 5 | |
G | 1 | 0 | ||||
A | 2 | 1 | ||||
M | 3 | 2 | ||||
B | 4 | 3 | ||||
O | 5 | 4 | ||||
L | 6 | 5 |
步驟3-6,當 i = 2
v0 | v1 | |||||
G | U | M | B | O | ||
0 | 1 | 2 | 3 | 4 | 5 | |
G | 1 | 0 | 1 | |||
A | 2 | 1 | 1 | |||
M | 3 | 2 | 2 | |||
B | 4 | 3 | 3 | |||
O | 5 | 4 | 4 | |||
L | 6 | 5 | 5 |
步驟3-6,當 i = 3
v0 | v1 | |||||
G | U | M | B | O | ||
0 | 1 | 2 | 3 | 4 | 5 | |
G | 1 | 0 | 1 | 2 | ||
A | 2 | 1 | 1 | 2 | ||
M | 3 | 2 | 2 | 1 | ||
B | 4 | 3 | 3 | 2 | ||
O | 5 | 4 | 4 | 3 | ||
L | 6 | 5 | 5 | 4 |
步驟3-6,當 i = 4
v0 | v1 | |||||
G | U | M | B | O | ||
0 | 1 | 2 | 3 | 4 | 5 | |
G | 1 | 0 | 1 | 2 | 3 | |
A | 2 | 1 | 1 | 2 | 3 | |
M | 3 | 2 | 2 | 1 | 2 | |
B | 4 | 3 | 3 | 2 | 1 | |
O | 5 | 4 | 4 | 3 | 2 | |
L | 6 | 5 | 5 | 4 | 3 |
步驟3-6,當 i = 5
v0 | v1 | |||||
G | U | M | B | O | ||
0 | 1 | 2 | 3 | 4 | 5 | |
G | 1 | 0 | 1 | 2 | 3 | 4 |
A | 2 | 1 | 1 | 2 | 3 | 4 |
M | 3 | 2 | 2 | 1 | 2 | 3 |
B | 4 | 3 | 3 | 2 | 1 | 2 |
O | 5 | 4 | 4 | 3 | 2 | 1 |
L | 6 | 5 | 5 | 4 | 3 | 2 |
步驟7
編輯距離就是矩陣右下角的值,v1[m] == 2。由”GUMBO”變換為”GAMBOL”的過程對於我來說是很只管的,即通過將”A”替換為”U”,並在末尾追加”L”這樣子(實際上替換的過程是由移除和插入兩個操作組合而成的)。
改良
如果您確信你的字串永遠不會超過2^16(65536)個字元,那麼你可以使用ushort來表示而不是int,如果字串少於2^8個,還可以使用byte。我覺得這個演算法用非託管程式碼實現的話可能會更快,但我沒有試過。
參考文獻
下載程式碼請前往原文:http://www.codeproject.com/Articles/13525/Fast-memory-efficient-Levenshtein-algorithm