使用說明
先看手冊上 levenshtein() 函數的說明:
levenshtein() 函數返回兩個字串之間的 Levenshtein 距離。
Levenshtein 距離,又稱編輯距離,指的是兩個字串之間,由一個轉換成另一個所需的最少編輯操作次數。許可的編輯操作包括將一個字元替換成另一個字元,插入一個字元,刪除一個字元。
例如把 kitten 轉換為 sitting:
sitten (k→s)
sittin (e→i)
sitting (→g)levenshtein() 函數給每個操作(替換、插入和刪除)相同的權重。不過,您可以通過設定可選的 insert、replace、delete 參數,來定義每個操作的代價。
文法:
levenshtein(string1,string2,insert,replace,delete)
參數 描述
string1 必需。要對比的第一個字串。
string2 必需。要對比的第二個字串。
insert 可選。插入一個字元的代價。預設是 1。
replace 可選。替換一個字元的代價。預設是 1。
delete 可選。刪除一個字元的代價。預設是 1。
提示和注釋
如果其中一個字串超過 255 個字元,levenshtein() 函數返回 -1。
levenshtein() 函數對大小寫不敏感。
levenshtein() 函數比 similar_text() 函數更快。不過,similar_text() 函數提供需要更少修改的更精確的結果。
例子
複製代碼 代碼如下:<?php
echo levenshtein("Hello World","ello World");
echo "<br />";
echo levenshtein("Hello World","ello World",10,20,30);
?>
輸出: 1 30
源碼分析
levenshtein() 屬於標準函數,在/ext/standard/目錄下有專門針對此函數實現的檔案:levenshtein.c。
levenshtein()會根據參數個數選擇實現方式,針對參數為2和參數為5的情況,都會調用 reference_levdist() Function Compute距離。其不同在於對後三個參數,參數為2時,使用預設值1。
並且在實現源碼中我們發現了一個在文檔中沒有說明的情況: levenshtein() 函數還可以傳遞三個參數,其最終會調用 custom_levdist() 函數。它將第三個參數作為自訂函數的實現,其調用樣本如下:
複製代碼 代碼如下:echo levenshtein("Hello World","ello World", 'strsub');
執行會報Warning: The general Levenshtein support is not there yet。這是因為現在這個方法還沒有實現,僅僅是放了一個坑在那。
reference_levdist() 函數的實現演算法是一個經典的DP問題。
給定兩個字串x和y,求最少的修改次數將x變成y。修改的規則只能是如下三種之一:刪除、插入、改變。
用a[i][j]表示把x的前i個字元變成y的前j個字元所需的最少操作次數,則狀態轉移方程為:
複製代碼 代碼如下:當x[i]==y[j]時:a[i][j] = min(a[i-1][j-1], a[i-1][j]+1, a[i][j-1]+1);
當x[i]!=y[j]時:a[i][j] = min(a[i-1][j-1], a[i-1][j], a[i][j-1])+1;
在用狀態轉移方程前,我們需要初始化(n+1)(m+1)的矩陣d,並讓第一行和列的值從0開始增長。 掃描兩字串(nm級的),對比字元,使用狀態轉移方程,最終$a[$l1][$l2]為其結果。
簡單實現過程如下:
複製代碼 代碼如下:<?PHP
$s1 = "abcdd";
$l1 = strlen($s1);
$s2 = "aabbd";
$l2 = strlen($s2);
for ($i = 0; $i < $l1; $i++) {
$a[0][$i + 1] = $i + 1;
}
for ($i = 0; $i < $l2; $i++) {
$a[$i + 1][0] = $i + 1;
}
for ($i = 0; $i < $l2; $i++) {
for ($j = 0; $j < $l1; $j++) {
if ($s2[$i] == $s1[$j]) {
$a[$i + 1][$j + 1] = min($a[$i][$j], $a[$i][$j + 1] + 1, $a[$i + 1][$j] + 1);
}else{
$a[$i + 1][$j + 1] = min($a[$i][$j], $a[$i][$j + 1], $a[$i + 1][$j]) + 1;
}
}
}
echo $a[$l1][$l2];
echo "n";
echo levenshtein($s1, $s2);
在PHP的實現中,實現者在注釋中很清楚的標明:此函數僅最佳化了記憶體使用量,而沒有考慮速度,從其實現演算法看,時間複雜度為O(m×n)。其最佳化點在於將上面的狀態轉移方程中的二維數組變成了兩個一組數組。簡單實現如下:複製代碼 代碼如下:<?PHP
$s1 = "abcjfdkslfdd";
$l1 = strlen($s1);
$s2 = "aab84093840932bd";
$l2 = strlen($s2);
$dis = 0;
for ($i = 0; $i <= $l2; $i++){
$p1[$i] = $i;
}
for ($i = 0; $i < $l1; $i++){
$p2[0] = $p1[0] + 1;
for ($j = 0; $j < $l2; $j++){
if ($s1[$i] == $s2[$j]){
$dis = min($p1[$j], $p1[$j + 1] + 1, $p2[$j] + 1);
}else{
$dis = min($p1[$j] + 1, $p1[$j + 1] + 1, $p2[$j] + 1); // 注意這裡最後一個參數為$p2
}
$p2[$j + 1] = $dis;
}
$tmp = $p1;
$p1 = $p2;
$p2 = $tmp;
}
echo "n";
echo $p1[$l2];
echo "n";
echo levenshtein($s1, $s2);
如上為PHP核心開發人員對前面經典DP的最佳化,其最佳化點在於不停的複用兩個一維數組,一個記錄上次的結果,一個記錄這一次的結果。如果按照PHP的參數,分別給三個操作賦值不同的值,在上面的演算法中將對應的1變成操作對應的值就可以了。 min函數的第一個參數對應的是修改,第二個參數對應的是刪除源碼天空,第三個參數對應的是添加。
Levenshtein distance說明
Levenshtein distance最先是由俄國科學家Vladimir Levenshtein在1965年發明,用他的名字命名。不會拼讀,可以叫它edit distance(編輯距離)。Levenshtein distance可以用來:
Spell checking(拼字檢查)
Speech recognition(語句識別)
DNA analysis(DNA分析)
Plagiarism detection(抄襲檢測) LD用mn的矩陣儲存距離值。