LeetCode | 208. 实现 Trie (前缀树)
原题(Medium):
实现一个 Trie (前缀树),包含 insert
, search
, 和 startsWith
这三个操作。
说明:
- 你可以假设所有的输入都是由小写字母
a-z
构成的。 - 保证所有输入均为非空字符串。
思路:
我还是第一次接触到前缀树这个结构,所以这里就先介绍一下何为前缀树。前缀树是一个有根的树,但一般根节点为空,其每个节点具有一下特点:
- 最多可有R个子节点,其中每个子节点对应字母表一个字母,本题设定R为26。
- 一个布尔变量,以指定节点是对应键的结尾还是键的前缀
其中键为一个完整的字符串。
向前缀树插入键,即如何向前缀树中插入一个完整的字符串呢。我们可以从根节点出发(空),搜寻其子节点集合里是否已经有对应于字符串第一个字符的字符:
- 如果有,那么进入该字符对应的子节点,继续搜索下一个字符串字符
- 如果不存在,就为当前节点创建一个子节点,该节点与当前遍历到字符对应
如何对应呢?我们知道每个节点存在一个子节点集合,我们可以使用指针数组来表示该集合。我们限定一个节点至多只有26个子节点,所以数组大小为26。既然如此,我们可以利用数组下标来对应字母表里的字母,例如0对应a,1对应b......如此类推,那么如何做到这般的对应呢?使用插入或检索时,字符变量与字符常量a相减得到的结果就是其对应的数字。
重复以上步骤,直到到达键的最后一个字符,然后将当前节点标记为结束节点,算法完成。
在了解插入如何实现之后,实现查找(search)和前缀查找(startsWith)就不困难了,都是同样的思路。
1 //一个节点最多拥有26个节点 2 const int MAXN = 26; 3 class Trie { 4 public: 5 /** Initialize your data structure here. */ 6 //判断当前结点是否是字符串尾部 7 bool is_str; 8 //当前节点的子节点数组,最多26个 9 Trie* next[MAXN]; 10 11 Trie():is_str(false) { 12 //默认构造函数 13 memset(next,0,sizeof(next)); //把指针数组的26个元素(26个指针)初始化为0 14 } 15 16 /** Inserts a word into the trie. */ 17 void insert(string word) { 18 //从根节点开始 19 Trie* cur = this; 20 //搜寻其子节点集合里是否已经有对应于字符串第一个字符的字符 21 for(char c : word) 22 { 23 //如果不存在,就为当前节点创建一个子节点,该子节点与当前遍历到字符对应 24 if(cur->next[c-'a'] == NULL) 25 { 26 Trie *new_node = new Trie(); 27 cur->next[c-'a'] = new_node; 28 } 29 //如果有,那么进入该字符对应的子节点,继续搜索下一个字符串字符 30 cur = cur->next[c-'a']; 31 } 32 //在把字符串完整地记录到树后,设置当前节点(即字符串最后一个字母的那个节点)的标记is_str,认为当前节点已经是一个完整的字符串了 33 cur->is_str = true; 34 } 35 36 /** Returns if the word is in the trie. */ 37 bool search(string word) { 38 Trie* cur = this; 39 for(char c : word) 40 { 41 //判断每一个字符对应的trie数组索引是否存在 42 if(cur->next[c-'a'] !=NULL) 43 cur = cur->next[c-'a']; //存在就进入到索引对应的trie对象中 44 else return false; //途中发现某个字符对应的下标不存在,就可以停止遍历并返回false了 45 } 46 //如果遍历完字符串后,发现当前节点确实是字符串结尾,就可以返回true了。这是防止一单词与树中记录的某一单词的前缀相同的情况。(事实上树就没记录该单词,只是树中刚好有单词的前缀跟其相同) 47 if(cur->is_str) return true; 48 else return false; 49 } 50 51 /** Returns if there is any word in the trie that starts with the given prefix. */ 52 //如果是单纯查找前缀的,就不用考虑当前节点是否是字符串结尾了,能遍历完参数的字符串就返回true,否则false 53 bool startsWith(string prefix) { 54 Trie* cur = this; 55 for(char c : prefix) 56 { 57 if(cur->next[c-'a'] !=NULL) 58 cur = cur->next[c-'a']; 59 else return false; 60 } 61 return true; 62 } 63 }; 64 65 /** 66 * Your Trie object will be instantiated and called as such: 67 * Trie* obj = new Trie(); 68 * obj->insert(word); 69 * bool param_2 = obj->search(word); 70 * bool param_3 = obj->startsWith(prefix); 71 */