超酷演算法(1):BK樹

威士忌發表於2014-10-22

這是『超酷演算法』系列的第一篇文章。基本上,任何一種演算法我覺得都很酷,尤其是那些不那麼明顯簡單的演算法。

BK樹或者稱為Burkhard-Keller樹,是一種基於樹的資料結構,被設計於快速查詢近似字串匹配,比方說拼寫檢查器,或模糊查詢,當搜尋”aeek”時能返回”seek”和”peek”。為何BK-Trees這麼酷,因為除了窮舉搜尋,沒有其他顯而易見的解決方法,並且它能以簡單和優雅的方法大幅度提升搜尋速度。

BK樹在1973年由Burkhard和Keller第一次提出,論文在這《Some approaches to best match file searching》。這是網上唯一的ACM存檔,需要訂閱。更細節的內容,可以閱讀這篇論文《Fast Approximate String Matching in a Dictionary》。

在定義BK樹之前,我們需要預先定義一些操作。為了索引和搜尋字典,我們需要一種比較字串的方法。編輯距離( Levenshtein Distance)是一種標準的方法,它用來表示經過插入、刪除和替換操作從一個字串轉換到另外一個字串的最小操作步數。其它字串函式也同樣可接受(比如將調換作為原子操作),只要能滿足以下一些條件。

現在我們觀察下編輯距離:構造一個度量空間(Metric Space),該空間內任何關係滿足以下三條基本條件:

  • d(x,y) = 0 <-> x = y (假如x與y的距離為0,則x=y)
  • d(x,y) = d(y,x) (x到y的距離等同於y到x的距離)
  • d(x,y) + d(y,z) >= d(x,z)

上述條件中的最後一條被叫做三角不等式(Triangle Inequality)。三角不等式表明x到z的路徑不可能長於另一箇中間點的任何路徑(從x到y再到z)。看下三角形,你不可能從一點到另外一點的兩側再畫出一條比它更短的邊來。

編輯距離符合基於以上三條所構造的度量空間。請注意,有其它更為普遍的空間,比如歐幾里得空間(Euclidian Space),編輯距離不是歐幾里得的。既然我們瞭解了編輯距離(或者其它類似的字串距離函式)所表達的度量的空間,再來看下Burkhard和Keller所觀察到的關鍵結論。

假設現在我們有兩個引數,query表示我們搜尋的字串,n表示字串最大距離,我們可以拿任意字串test來跟query進行比較。呼叫距離函式得到距離d,因為我們知道三角不等式是成立的,所以所有結果與test的距離最大為d+n,最小為d-n。

由此,BK樹的構造就相當簡單:每個節點有任意個子節點,每條邊有個值表示編輯距離。所有子節點到父節點的邊上標註n表示編輯距離恰好為n。比如,我們有棵樹父節點是”book”和兩個子節點”rook”和”nooks”,”book”到”rook”的邊標號1,”book”到”nooks”的邊上標號2。

從字典裡構造好樹後,取任意單詞作為樹的根節點。無論何時你想插入新單詞時,計算該單詞與根節點的編輯距離,並且查詢數值為d(neweord, root)的邊。遞迴得與各子節點進行比較,直到沒有子節點,你就可以建立新的子節點並將新單詞儲存在那。比如,插入”boon”到剛才上述例子的樹中,我們先檢查根節點,查詢d(“book”, “boon”) = 1的邊,然後檢查標號為1的邊的子節點,得到單詞”rook”。我們再計算距離d(“rook”, “boon”)=2,則將新單詞插在”rook”之後,邊標號為2。

在樹中做查詢,計算單詞與根節點的編輯距離d,然後遞迴查詢每個子節點標號為d-n到d+n(包含)的邊。假如被檢查的節點與搜尋單詞的距離d小於n,則返回該節點並繼續查詢。

BK樹是多路查詢樹,並且是不規則的(但通常是平衡的)。試驗表明,1個查詢的搜尋距離不會超過樹的5-8%,並且2個錯誤查詢的搜尋距離不會超過樹的17-25%,這可比檢查每個節點改進了一大步啊!需要注意的是,如果要進行精確查詢,也可以非常有效地通過簡單地將n設定為0進行。

回顧這篇文章,寫的有點長哈,似乎比我預期中的要複雜。希望你在閱讀之後,也能感受到BK樹的優雅和簡單。

相關文章