簡介:中等,題意為實作Trie (prefix tree 前綴樹),可以使用Linked list, map, set去實作。
一、Linked list
(一)結構
用Linked list建構的trie,其紀錄abc和abde兩字。 |
上圖為用linked list建構的trie,每一個節點有兩個資料,分別為:
- isRecorded:這個字是否被紀錄,如:abc和abde有被記錄,但ab沒被記錄。
- children:一個指向節點的26格陣列,每一格代表一個英文字母,如果指向NULL,則代表此字母沒有被紀錄;如果有指向節點,則代表此字母有被紀錄。
以下為程式碼:
class TrieNode { public: bool isRecorded; std::vector<TrieNode*> children; TrieNode() :isRecorded(false), children(std::vector(26, NULL)){} }; class Trie { public: TrieNode* root; Trie():root(new TrieNode()){} void insert(std::string word); bool search(std::string word); bool startsWith(std::string prefix); };
(二)插入字詞
void Trie::insert(std::string word) { TrieNode* node = root; for (auto w : word) { const int index = w - 'a'; // If the node have not a corresponding child, add a new child to it. if (node->children[index] == NULL) {node->children[index] = new TrieNode();} // Move to the next node node = node->children[index]; } node->isRecorded = true; }
以上是insert()程式碼,時間複雜度為字詞 (word)長度。
- Line 3 for loop:只要把字詞的每一個字母依照順序和trie做比較即可。
- Line 6:如果此字母指向NULL,就讓他指向一個新節點。
- Line 8:移至下一個節點。
- Line 10:比較結束後,更新isRecorded為true。
(三)搜尋字詞
bool Trie::search(std::string word) { TrieNode* node = root; for (auto w : word) { const int index = w - 'a'; // If the node have not a corresponding child, the trie does not store the word. if (node->children[index] == NULL) {return false;} // Move to the next node node = node->children[index]; } return node->isRecorded; }
以上是search()程式碼,時間複雜度為字詞 (word)長度。搜尋的邏輯和插入很像,都要每個字母和trie做比較,比較不同的部分為:
- Line 6:如果此字母指向NULL,就直接回傳false,因為此字母沒有被記錄。
- Line 10:比較結束後,直接回傳isRecorded。
(四)搜尋前綴
bool Trie::startsWith(std::string prefix) { TrieNode* node = root; for (auto p : prefix) { const int index = p - 'a'; // If the node have not a corresponding child, the trie does not store the prefiex. if (node->children[index] == NULL) {return false;} // Move to the next node node = node->children[index]; } return true; }
以上是startWith()程式碼,時間複雜度為字詞 (word)長度。搜尋前綴和字詞的邏輯一樣,只不過在Line10中,直接回傳true。
二、Map and set
(一)Map
用map建構的trie,其紀錄abc和abde兩字。 |
上圖為用linked list建構的trie,這裡使用兩個Map分別去紀錄前綴 (prefix)與字詞 (word),以下為程式碼。
class TrieMap { public: std::map<std::string, bool> prefixMap; std::map<std::string, bool> wordMap; TrieMap(){} void insert(std::string word); bool search(std::string word); bool startsWith(std::string prefix); }; void TrieMap::insert(std::string word) { // Store word wordMap[word] = true; // Store prefix string key = ""; for (int i = 0; i < word.size(); ++i) { key += word[i]; prefixMap[key] = true; } } bool TrieMap::search(std::string word) { return wordMap[word]; } bool TrieMap::startsWith(std::string prefix) { return prefixMap[prefix]; }
插入有兩部分:
- Word:直接加入字詞,時間複雜度為 log(字詞總數)。
- Prefix:每個前綴依序加入,時間複雜度為 字綴長度*log(前綴總數)。
搜尋字詞與前綴就直接回傳map即可,如果不在map裡的字,operator[]會將此字加入map中,並給予false。
(二)Set
在Map中,我們使用兩個map去紀錄word和prefix,但事實上我們只需要wordMap即可。然後你應該會發現,map紀錄的true很冗餘吧?所以我們也不用map,只要set就好。實作如下:
class TrieSet { public: std::set<std::string> strSet; TrieSet(){} void insert(std::string word); bool search(std::string word); bool startsWith(std::string prefix); }; void TrieSet::insert(std::string word) { strSet.insert(word); } bool TrieSet::search(std::string word) { return strSet.find(word) != strSet.end(); } bool TrieSet::startsWith(std::string prefix) { auto it = strSet.lower_bound(prefix); if (it == strSet.end()) {return false;} for (size_t i = 0; i < prefix.size(); ++i) { if (prefix[i] != (*it)[i]) {return false;} } return true; }
插入就直接把字詞放入集合中,而搜尋字詞就在集合中搜尋此字即可。最後搜尋前綴就要利用lower_bound()去找出和字詞最像的元素出來,然後比較這兩前綴是否一致?
三、相關文章
請詳見LeetCode相關文章
留言
張貼留言