1. 程式人生 > 實用技巧 >字典(Trie)樹:如何實現搜尋引擎的搜尋關鍵詞提示功能?

字典(Trie)樹:如何實現搜尋引擎的搜尋關鍵詞提示功能?

  Trie 樹,也叫“字典樹”。顧名思義,它是一個樹形結構。它是一種專門處理字串匹配的資料結構,用來解決在一組字串集合中快速查詢某個字串的問題。它並不是二叉樹,而是“多叉樹”。

class TrieNode {
  char data;
  TrieNode children[26];
}

  剛剛我們在講 Trie 樹的實現的時候,講到用陣列來儲存一個節點的子節點的指標。如果字串中包含從 a 到 z 這 26 個字元,那每個節點都要儲存一個長度為 26 的陣列,並且每個陣列元素要儲存一個 8 位元組指標(或者是 4 位元組,這個大小跟 CPU、作業系統、編譯器等有關)。而且,即便一個節點只有很少的子節點,遠小於 26 個,比如 3、4 個,我們也要維護一個長度為 26 的陣列。

  我們前面講過,Trie 樹的本質是避免重複儲存一組字串的相同字首子串,但是現在每個字元(對應一個節點)的儲存遠遠大於 1 個位元組。按照我們上面舉的例子,陣列長度為 26,每個元素是 8 位元組,那每個節點就會額外需要 26*8=208 個位元組。而且這還是隻包含 26 個字元的情況。

  如果字串中不僅包含小寫字母,還包含大寫字母、數字、甚至是中文,那需要的儲存空間就更多了。所以,也就是說,在某些情況下,Trie 樹不一定會節省儲存空間。在重複的字首並不多的情況下,Trie 樹不但不能節省記憶體,還有可能會浪費更多的記憶體。

  當然,我們不可否認,Trie 樹儘管有可能很浪費記憶體,但是確實非常高效。那為了解決這個記憶體問題,我們是否有其他辦法呢?

  我們可以稍微犧牲一點查詢的效率,將每個節點中的陣列換成其他資料結構,來儲存一個節點的子節點指標。用哪種資料結構呢?我們的選擇其實有很多,比如有序陣列、跳錶、散列表、紅黑樹等。

  實際上,字串的匹配問題,籠統上講,其實就是資料的查詢問題。對於支援動態資料高效操作的資料結構,我們前面已經講過好多了,比如散列表、紅黑樹、跳錶等等。實際上,這些資料結構也可以實現在一組字串中查詢字串的功能。我們選了兩種資料結構,散列表和紅黑樹,跟 Trie 樹比較一下,看看它們各自的優缺點和應用場景。

  在剛剛講的這個場景,在一組字串中查詢字串,Trie 樹實際上表現得並不好。它對要處理的字串有及其嚴苛的要求。

  第一,字串中包含的字符集不能太大。我們前面講到,如果字符集太大,那儲存空間可能就會浪費很多。即便可以優化,但也要付出犧牲查詢、插入效率的代價。

  第二,要求字串的字首重合比較多,不然空間消耗會變大很多。

  第三,如果要用 Trie 樹解決問題,那我們就要自己從零開始實現一個 Trie 樹,還要保證沒有 bug,這個在工程上是將簡單問題複雜化,除非必須,一般不建議這樣做。

  第四,我們知道,通過指標串起來的資料塊是不連續的,而 Trie 樹中用到了指標,所以,對快取並不友好,效能上會打個折扣。

  綜合這幾點,針對在一組字串中查詢字串的問題,我們在工程中,更傾向於用散列表或者紅黑樹。因為這兩種資料結構,我們都不需要自己去實現,直接利用程式語言中提供的現成類庫就行了。

  講到這裡,你可能要疑惑了,講了半天,我對 Trie 樹一通否定,還讓你用紅黑樹或者散列表,那 Trie 樹是不是就沒用了呢?是不是今天的內容就白學了呢?

  實際上,Trie 樹只是不適合精確匹配查詢,這種問題更適合用散列表或者紅黑樹來解決。Trie 樹比較適合的是查詢字首匹配的字串,也就是類似開篇問題的那種場景。