前言今天在寫LeetCode每日一題時遇到208. Implement Trie (Prefix Tree) ,之前因為覺得Trie好像很難所以一直不願面對,但這題就是要跟Trie的直球對決了想躲也躲不掉XD,Anyway,看了一下發現Trie其實蠻好理解的,今天就來認識一下Trie,然後看看這一題可以怎麼寫吧!(Python, C++)
Trie 介紹Trie的中文可以叫做字典樹、字首樹或前綴樹,顧名思義是一種樹狀結構,常被用來檢索文本中的單詞或前綴(prefix)。
字典樹有幾個性質,這裡搭配下方圖片做介紹:
每個節點代表一個字符,並且單字的結尾有標記,舉例來說單字"ten"會依序經過t、e、n三個節點,其中n是結尾(綠色)。 節點是可以被共用的,舉例來說"sun"及"sup"就共用了s、u兩個節點。 葉子節點一定是單字結尾,但非葉節點也可以是結尾,舉例來說"an"及"and"分別是兩個單字,其中n及d都是單字的結尾(綠色)。 字典樹的優點是可以快速查找字符串,且可以按照字典序進行排序。但缺點是需要較大的空間來存儲,且插入和刪除操作相對較慢。
實作 Trie實作的內容就是一開始提到的LeetCode 208,簡單來說就是要做出初始化、插入、尋找及尋找前綴4個功能,詳細題目可以自己到LeetCode去看。
值得一提的是,Trie 有兩種實作方法,第一個方法相當直覺,就是用指針連結各個節點來模擬出一個樹狀結構;第二個方法則是動態語言限定,我們可以直接用hash table來儲存字符,接下來分別用C++及Python來演示這兩種方法。
方法一: Pointers (C++)重點
英文字母只有26個,所以可以直接使用array,以index(w-'a'
)來表示字符。 prefix的部分只要節點存在就OK,但search的部分還要檢查是不是結尾。 另外定義一個struct而不是直接new原本的class,是因為struct的結構比class簡單的多(沒有function),可以提升執行效率。 使用靜態宣告(array)所以不用擔心memory leak。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 struct Node { Node* children[26 ]; bool is_word = false ; }; class Trie {public : Node* root; Trie () { root = new Node (); } void insert (string word) { Node* curr = root; for (char w: word) { if (curr->children[w-'a' ] == nullptr ){ curr->children[w-'a' ] = new Node (); } curr = curr->children[w-'a' ]; } curr->is_word = true ; } bool search (string word) { Node* curr = root; for (char w: word) { if (curr == NULL || curr->children[w-'a' ] == nullptr ){ return false ; } curr = curr->children[w-'a' ]; } return curr->is_word; } bool startsWith (string prefix) { Node* curr = root; for (char w: prefix) { if (curr->children[w-'a' ] == nullptr ){ return false ; } curr = curr->children[w-'a' ]; } return true ; } };
方法二: Hash Table (Python)重點
用特殊符號(#)來標記單字結尾。 因為動態語言中的資料型態是動態決定的,所以Hash Table內可以同時儲存不同資料型態(存下一層/存結尾)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Trie : def __init__ (self ): self._root = {} def insert (self, word: str ) -> None : curr = self._root for w in word: if w not in curr: curr[w] = {} curr = curr[w] curr["#" ] = True def search (self, word: str ) -> bool : curr = self._root for w in word: if w in curr: curr = curr[w] else : return False return curr.get("#" , False ) def startsWith (self, prefix: str ) -> bool : curr = self._root for w in prefix: if w in curr: curr = curr[w] else : return False return True
相關文章Trie (字典樹) - Design Add and Search Words Data Structure
結語Trie的基本觀念說實在還蠻簡單的,難應該是難在之後的應用,之後有機會看能不能更新一些進階的觀念,像是搜尋引擎之類的。
Referencehttps://www.youtube.com/watch?v=f48wGD-MuQw