目录
1 前缀树原理简介
先来简单介绍一下前缀树是什么。前缀树也叫字典树,常用语字符串的查找,为什么又叫前缀树呢?我们先来看看前缀树长什么样子:
如图所示,我们把"app"、“and”、“bad”以及“ban”放在树中,每个字符串都是从root开始的,然后根据第一个元素的不同分成了"a"开头与"b"开头两组,然后接着往下走,"a"开头的又分开了,而“b”开头的两个字符串还没有分开,再往下走,“b”开头的两个字符串也分开了。从整个树的结构可以看得出来,字符串两两之间的相同前缀部分是重合的,一旦分开就不会再组合了,因此在一组字符串中查找某一个字符串的时候可以避免很多不必要的查找。
好了,简单介绍了前缀树之后,还有几点也必须考虑:既然前缀树也是树,那么每个结点的数据类型应当是什么呢?首先,根据前缀树的结构可以看出来,每个树的结点都是需要指向子结点的,那么子结点应该有几个呢?子结点的个数我们是无法确定的,就像图中的结点,有的结点有两个子结点,有的结点只有一个子结点,那该怎么办呢?
实际上也很简单,如果前缀树中只可能出现多少种结点情况那就定义多少个子结点!比如说这里如果前缀树中的都是小写字母的话,那就直接定义26个子结点就好了。有了子结点,那每个结点需不需要再定义一个value来存放到底是'a'还是'b'呢?实际上根本就不需要,因为既然前面定义了26个子结点,那么每个子结点就已经确定好了,无需再定义一个value。现在,有了子结点,查找的时候就可以顺着子结点往下搜索了,那我什么时候才叫搜索到什么时候结束呢?比如说我要查找“and”,那就应该到'd'结束,那我怎么知道结束了呢?将26个结点全部遍历一次全为空?如果这样的话,如果树中还包含数字之类的话显然效率就很低了,因此,我们直接再给每个结点定义一个末尾标记变量,如果这个结点是其所在的这条线的最后一个结点,那就标记为末尾,那又怎么去标记呢?我们下面就用例题来说了。先来看看每个结点的数据类型:
struct TrieNode{
TrieNode* child[26]; //子结点
bool endFlag; //末尾标记,是则true,否则false
TrieNode():endFlag(false)
{
for(auto &c:child)c=NULL; //初始化末尾标记为false,每个子结点为NULL
}
};
2 实现前缀树
2.1 题目描述
实现一个 Trie (前缀树),包含 insert
, search
, 和 startsWith
这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
说明:
- 你可以假设所有的输入都是由小写字母
a-z
构成的。 - 保证所有输入均为非空字符串。
2.2 题目分析
题