LeetCode | 208. 实现 Trie (前缀树)

原题Medium):

  实现一个 Trie (前缀树),包含 insertsearch, 和 startsWith 这三个操作。

    

说明:

  • 你可以假设所有的输入都是由小写字母 a-z 构成的。
  • 保证所有输入均为非空字符串。

 

思路:

  我还是第一次接触到前缀树这个结构,所以这里就先介绍一下何为前缀树。前缀树是一个有根的树,但一般根节点为空,其每个节点具有一下特点:

  • 最多可有R个子节点,其中每个子节点对应字母表一个字母,本题设定R为26。
  • 一个布尔变量,以指定节点是对应键的结尾还是键的前缀

  其中键为一个完整的字符串。

 

  向前缀树插入键,即如何向前缀树中插入一个完整的字符串呢。我们可以从根节点出发(空),搜寻其子节点集合里是否已经有对应于字符串第一个字符的字符:

  1. 如果有,那么进入该字符对应的子节点,继续搜索下一个字符串字符
  2. 如果不存在,就为当前节点创建一个子节点,该节点与当前遍历到字符对应

  如何对应呢?我们知道每个节点存在一个子节点集合,我们可以使用指针数组来表示该集合。我们限定一个节点至多只有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  */

posted @ 2019-10-31 22:54  羽园原华  阅读(226)  评论(0编辑  收藏  举报