[轉]雙數組TRIE樹原理


原文名稱: 
An Efficient Digital Search Algorithm by Using a Double-Array Structure 
作者: 
JUN-ICHI AOE 
譯文: 
使用雙數組結構的一個高效的Digital Search算法 

摘要: 
本文介紹了一種新的內部(內部排序的內部,也就是在內存里)數組結構的digital search算法,叫做雙數組,結合了數組存取的快速和鏈式存儲的壓縮。Digital search樹的每一條弧在雙數組中都可以以O(1)的時間復雜度計算得到;也就是說,查找一個key值最壞的時間復雜度是O(k),k是這個key值的長度。本文給出了同時具有速度和空間雙重性能的雙數組的查找,插入,刪除算法。假設雙數組的長度是n+cm,n是ds樹中節點的數量,m是輸入符號的數量,c是一個依賴於實現的常數;那么理論上可以證明插入和刪除的最壞時間復雜度分別是cm2(插入要解決沖突,所以慢)和cm,與n沒有關系。從實驗的結果來看,建立雙數組的時間隨n增長,並且c是一個相當小的常數,從0.17到1.13。

關鍵詞:數據庫系統,數據結構,詞典,digital search,動態內部存儲,關鍵詞查詢算法。 --- 


1.       導論 
在很多信息檢索算法中,很需要采用一種快速的digital search算法,或者叫做trie搜索,因為它一個字符(digital)一 個字符地查看輸入。使用這種數據結構的例子有一種詞法分析器,和一種編譯器的本地代碼優化器,一種圖書搜索,拼寫檢查,常用單詞過濾器,一種自然語言處理 中的形態學分析器等等。詞典能夠動態增長在自然語言處理中尤為重要,因為經常需要對詞匯表添加新詞(這其實是雙數組的弱項- -)。本文展示的這一算法適合插入遠遠多於刪除的情況,這樣刪除帶來的空間浪費就可以由插入來填補。

       關鍵詞查詢策略可以被大致分為兩類,按照關鍵詞集合是否可變可以將這些算法分為“動態方法”(允許查詢表被修改)和“靜態方法”(顯然相反)兩種。廣為人知的“動態方法”有:hashing,二叉樹,B+樹,擴展hashing,和trie hashing。而“靜態方法”有:完美hashing,稀疏表,以及壓縮trie。 當使用靜態方法的時候我們能專注於提高查詢速度和壓縮數據結構,而當使用動態方法的時候我們會使用額外的空間以達到更快的更新速度。本文提出的查詢方法正 好介於這兩者之間,所以我稱之為“弱靜態方法”。將靜態方法擴展到弱靜態方法,同時保持前者有用的特性是十分困難的。完美hashing的擴展已經有了,但不能確定插入的時間復雜度上限。本文的目標是建立一種digital search算法,它同時具有靜態方法的速度和壓縮特性,以及動態方法的快速更新的能力。 

       不同於基於key值的搜索方法,digital search采用一連串的字符(digit)來表示一個key。每個h層的DS樹的節點表示所有以一定的h個字符開始的關鍵詞;這個節點根據第(h+1)個字符定義它的分支。本文的基本觀念是壓縮trie樹,使用兩個一維數組base和check來表示trie樹,成為雙數組,並且給出更新(插入、刪除)算法。Trie的每個節點使用指針指向下一個元素,每個索引元素是一個結束標志加上一個指向新節點的指針(或者null)。查詢,插入,刪除都非常快,但是它會占用很多空間,因為很多trie樹節點是空的;也就是說,trie樹是稀疏的。所以我們必須嘗試映射節點r到check數組,這種映射關系由base[r]指定。

       在接下來的章節,我們會詳細描述我們的想法。在第二節,我們把DS樹形式化為模式匹配機器並定義雙數組使用O(1)時間計算一條弧。為了將雙數組應用於大的關鍵詞集合,雙數組做出了一些修改。最主要的創新是僅將足以分辨不通關鍵詞的前綴存儲到雙數組,將其他部分存儲在單獨的string里面。插入刪除算法在第三章討論。當插入一個新的非空位置r的時候遇到另一個節點k已經占用這個位置,插入算法通過重新調整base[r]或者base[k]來解決沖突。在本文中,為減少占用的時間和空間占用組少非空未知的k或r有優先權(就是移動占用少的,占用多的不變)。第四節討論了每個算法的最壞時間復雜度的理論值,並且通過實驗驗證了。雙數組的部分匹配和key-order查詢也做出了討論。最后第五節做出了結論總結。

雙數組trie樹的基本構造及簡單優化 

一、 基本構造 

Trie樹是搜索樹的一種,來自英文單詞"Retrieval"的簡寫,可以建立有效的數據檢索組織結構,是中文匹配分詞算法中詞典的一種常見實現。它本質上是一個確定的有限狀態自動機(DFA),每個節點代表自動機的一個狀態。在詞典中這此狀態包括"詞前綴","已成詞"等。
雙數組Trie(Double-Array Trie)是trie樹的一個簡單而有效的實現,由兩個整數數組構成,一個是base[],另一個是check[]。設數組下標為i ,如果base,check均為0,表示該位置為空。如果base為負值,表示該狀態為詞語。Check表示該狀態的前一狀態,t=base+a, check[t]=i 。
復制代碼 
下面舉例(源自<<雙數組Trie(Double-Array Trie)的數據結構與具體實現>>)來說明用雙數組Trie(Double-Array Trie)構造分詞算法詞典的過程。假定詞表中只有“啊,阿根廷,阿膠,阿拉伯,阿拉伯人,埃及”這幾個詞,用Trie樹可以表示為:
我們首先對詞表中所有出現的10個漢字進行編碼:啊-1,阿-2,唉-3,根-4,膠-5,拉-6,及-7,廷-8,伯-9,人-10。。對於每一個漢字,需要確定一個base值,使得對於所有以該漢字開頭的詞,在雙數組中都能放下。例如,現在要確定“阿”字的base值,假設以“阿”開頭的詞的第二個字序列碼依次為a1,a2,a3……an,我們必須找到一個值i,使得base[i+a1],check[i+a1],base[i+a2],check[i+a2]……base[i+an],check[i+an]均為0。一旦找到了這個i,“阿”的base值就確定為i。用這種方法構建雙數組Trie(Double-Array Trie),經過四次遍歷,將所有的詞語放入雙數組中,然后還要遍歷一遍詞表,修改base值。因為我們用負的base值表示該位置為詞語。如果狀態i對應某一個詞,而且Base=0,那么令Base=(-1)*i,如果Base的值不是0,那么令Base=(-1)*Base。得到雙數組如下:
復制代碼 
下標 1 2 3 4 5 6 7 8 9 10 11 12 13 14 
Base -1 4 4 0 0 0 0 4 -9 4 -11 -12 -4 -14 
Check 0 0 0 0 0 0 0 2 2 2 3 8 10 13 
詞綴 啊 阿 埃 阿根 阿膠 阿拉 埃及 阿根廷 阿拉伯 阿拉伯人 
用上述方法生成的雙數組,將“啊”,“阿”,“埃”,“阿根”,“阿拉”,“阿膠”,“埃及”,“阿拉伯”,“阿拉伯人”,“阿根廷”均視為狀態。每個狀態均對應於數組的一個下標。例如設“阿根”的下標為i=8,那么check的內容是“阿”的下標,而base是“阿根廷”的下標的基值。“廷”的序列碼為x=8,那么“阿根廷”的下標為base+x=base[8]+8=12。
復制代碼 
二、 基本操作與存在問題 

1, 查詢 
trie樹的查詢過程其實就是一個DFA的狀態轉移過程,在雙數組中實現起來比較簡單:只需按照狀態標志進行狀態轉移即可.例如查詢“阿根廷”,先根據“阿”的序列碼b=2,找到狀態“阿”的下標2,再根據“根”的序列碼d=4找到“阿根”的下標base+d=8,同時根據check[base+d]=b,表明“阿根”是某個詞的一部分,可以繼續查詢。然后再找到狀態“阿根廷”。它的下標為y=12,此時base[y]<0,check[y]=base+d=8,表明“阿根廷”在詞表中,查詢完畢。
復制代碼 
查詢過程中我們可以看到,對於一個詞語的查詢時間是只與它的長度相關的,也就是說它的時間復雜度為O(1).在漢語中,詞語以單字詞,雙字詞居多,超過三字的詞語少之又少.因此,用雙數組構建的trie樹詞典查詢是理論上中文機械分詞中的最快實現。

2, 插入與刪除 
雙數組的缺點在於:構造調整過程中,每個狀態都依賴於其他狀態,所以當在詞典中插入或刪除詞語的時候,往往需要對雙數組結構進行全局調整,靈活性能較差。 

將一個詞語插入原有的雙數組trie樹中,相當於對DFA增加一個狀態。首先我們應根據查詢方法找出該狀態本應所處的位置,如果該位置為空,那好辦,直接插入即可。如果該位置不為空。那么我們只好按照構造時一樣的方法重新掃描得出該狀態已存在的最大前綴狀態的BASE值,並由此依次得出該狀態后繼結點的BASE值。在這其中還要注意CHECK值的相應變化。

例如說,如果"阿拉根"某一天也成為了一個詞,我們要在trie樹中插入這一狀態。按計算它的位置應在8,但8是一個已成狀態.所以我們得重新確定"阿拉"這一最大已成前綴狀態的BASE值.重新掃描得出BASE[10]=11。這樣狀態15為"阿拉根",且BASE[15]為負(成詞),CHECK[15]=10;狀態20為"阿拉佰",且BASE[20]=-4,CHECK=10。

這樣的處理其實是非常耗時間的,因為得依次對每一個可能BASE值進行掃描來進行確定最大已成前綴狀態的BASE值。這個確定過程在構造時還是基本可以忍受的,畢竟你就算用上一,兩天來構造也沒有問題(只要你構造完后可以在效運行即可)。但在插入比較頻繁時,如果每次都需要那么長的運行時間,那確實是無法忍受的。

雙數組刪除實現比較簡單,只需要將刪除詞語的對應狀態設為空即可――即BASE值,CHECK均為設0。但它存在存在一個空間效率的問題.例如,當我們在上面刪除"埃及"這一詞語時,狀態11被設為空。而狀態10則成了一個無用結點――它不成詞,而且在插入新詞時也不可重用。所以,隨着刪除的進行,空狀態點和無用狀態點不斷增多,空間的利用率會不斷的降低。

三、 簡單優化 

優化的基本思路是將雙數組trie樹構建為一種動態檢索方法,從而解決插入和刪除所存在的問題。 

1, 插入優化 
在插入需要確定新的BASE值時,我們是只需要遍歷空狀態的。非空狀態的出現意味着某個BASE值嘗試的打敗,我們可以完全不必理會。所以,我們可以對所有的空狀態構建一個序列,在確定BASE值時只需要掃描該序列即可。
對雙數組中的空狀態的遞增結點r1,r2, …, rm,我們可以這樣構建這一空序列: 
CHECK[ri]=−ri+1 (1 i m−1), 
CHECK[rm]=−(DA_SIZE+1) 
其中r1= E_HEAD,為第一個空值狀態對應的索引點。這樣我們在確定BASE值時只需掃描這一序列即可。這樣就省去了對非空狀態的訪問時間。 

這種方法在空狀態並不太多的情況下可以很大程度的提高插入速度。 

2, 刪除優化 
1) 無用結點 
對於刪除葉結點時產生的無用結點,可以通過依次判斷將它們置為空,使得可在插入新詞時得以重用。例如,如果我們刪除了上例中的"阿根廷",可以看到"阿根"這一狀態沒有子狀態,因此也可將它置為空。而"阿"這一狀態不能置空,因為它還有兩個子狀態。

2) 數組長度的壓縮 
在刪除了一個狀態后,數組末尾可能出現的連續空狀態我們是可以直接刪除的。另外我們還可以重新為最大非空索引點的狀態重新確定BASE值,因為它有可能已經由於刪除的進行而變小。這們我們可能又得以刪除一些空值狀態。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM