Approximate string matching
近似字符串匹配問題。
Levenshtein edit distance
在metric space中衡量兩個字符串差異程度的一個指標,即通過一系列單字符的編輯操作把字符串A變成字符串B所需的最小編輯次數,其中的編輯操作可以是插入字符、刪除字符以及修改字符。
計算編輯距離有個經典的Wagner-Fischer算法,使用了動態規劃,用d(i,j)表示把字符串A的前i個字符變成字符串B的前j個字符所需的最小編輯次數(即子串的編輯距離)。
下面給出Ruby實現:
1 | def levenshtein a, b |
另外,編輯距離是個metric,也就是說三個字符串間滿足三角不等式:
dist(a, b) + dist(b, c) >= dist(a, c)
可以這樣理解,把a變成b的最少修改操作和b變成c的最少修改操作拼起來就得到了把a變成c的一個可行修改操作,編輯次數爲dist(a,c),而最小編輯次數肯定不會比這個數大。
Burkhard-Keller tree
給定一個字符串集合S,有若干詢問。每個詢問的輸入是一個字符串a,輸出是字符串集合S
中和a的編輯距離在某個閾值內的所有字符串,即:{b | levenshtein(a,b) <= k},其中k是閾值。
直觀想法就是枚舉S中的每個串,一一和 a
求編輯距離,若在閾值內則輸出。對於長爲m和n的兩個串,樸素的Wagner-Fischer算法時間複雜度是O(m*n),又要對集合中每個串都做這樣的操作,比較慢。
於是就有兩個優化策略:
優化編輯距離的計算
對於k爲常數的情況,可以利用性質d(a, b) >= abs(len(a)-len(b)),動態規劃表格中很多格子的值必然大於k,而我們計算編輯距離的意圖是判斷是否在閾值內,所以對於這些值必然大於k的格子我們是不感興趣的,可以避免計算。我們只需要只計算一條diagonal
band,這樣可以把時間複雜度優化到O((m+n)*k)。
另外對於這樣的unit-cost Levenshtein edit distance問題,還可以採用一些bit-parallel方法進行常數優化。
減少字符串集合中的字符串枚舉次數
令a爲字符串集合中任意一個字符串,對於詢問字符串b,計算出它們的編輯距離dist(a, b)。對於每一個我們感興趣的字符串c,都滿足dist(c,b) <= k,而根據三角不等式,我們知道:
dict(a,b)-k <= dist(c,b) <= dist(a,b)+k
注意到所有的c都滿足這個不等式,也就是說我們不需要考慮整個字符串集合,只需要考慮和a的編輯距離在某個特定區間內的所有字符串。
如何獲取和樞軸a的編輯距離在某個特定區間內的所有字符串呢?只需要對於所有不同的編輯距離分類,令a有若干棵子樹,分別表示和a編輯距離爲0的字符串的集合、和a編輯距離爲1的字符串集合……而這些子樹的任務是:在子樹內找出所有和詢問串的編輯距離在閾值內所有字符串,和原問題形式相同,不難想到對子樹用同樣方法遞歸構建Burkhard-Keller樹。
然後考慮如何處理詢問。先測試根節點的字符串a和詢問串b的編輯距離是否在閾值k內,是則輸出。接着根據三角不等式得到一個區間[dist(a,b)-k .. dist(c,b)-k],得到一系列可能有候選字符串的子樹,遞歸處理。
下面給出Ruby實現:
1 | class BKTree |