前缀树
今天给大家讲解一下什么是前缀树,但是这篇文章并不是站在数据结构研发者的角度来讲解的,因为相信很多读者都和笔者一样也才是接触数据结构不久的小白,笔者写文章之前也看过很多描写前缀树的文章,如july大神讲解前缀树。但是这些文章大多数都讲的太过高深,并且大佬们的前缀树也都不是那么容易实现。所以今天笔者尽可能将这个数据结构给大家讲的简单一些。
大佬们的前缀树
前缀树概念:Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。(其实就是想说他使用了哈希的思想)
来看看一颗真正意义上的前缀树模型,我们虽然在后面会把某些知识简化,但是真正完整的树模型还是有必要看看的。
来看看这颗树有什么关键部分组成的:
- 树节点:树的节点中存放的是状态,判断字符串是否走到结尾了
- 边:每个边上有一个字符,从根开始的路径上的字符组成了串
说到这你可能还是不明白树节点中的状态有什么用,那么我们来一起用边上的字符组成一个串把。
仔细理解这个图,我们就会发现,如果我们接着往下走,abcd也是一个存在树中的串。说到这里,相信你已经理解了怎么从树中查找一个字符串,但是我相信你一定会有和笔者的困惑。
如何在树节点中存储状态呢?这是小问题,大问题是:怎么将字符存到这些所谓的边上呢?
在笔者看了很多大佬的代码之后,最后发现我们可以实现一颗简单一些的前缀树
小白们的字典树
基于我们没必要实现一颗真正意义上的原生前缀树,这里我们不妨来实现一颗可以用来查找仅仅由小写字母组成的前缀树吧。
先来一起看看我们的树模型吧:
要明白我们的树的结构,不得不着重给大家介绍这个不寻常的数组:
这个数组中,只有第二个位置我们画了一个箭头,这个箭头指向了下一个相似的这样的数组,而这个箭头所处数组的位置是字符b要映射的位置,实际上就是表明了这个节点中此位置的b字符存在
现在我们来看看如何表示一个abc串:
所以我们真正实现的前缀树的模型图如下图:
如果仔细的认真的看,你一定已经明白我们的前缀树是怎么实现的,现在我们一起来看看代码怎么写。
模拟实现前缀树
代码超级短哦:
const int MAXN = 26;
class Trie {
public:
bool is_str; // 标识当前结点是否为一个完整的字符串
Trie *next[MAXN]; // 下一个结点的指针数组
Trie() {
is_str = NULL;
memset(next, 0, sizeof(next));
}
/** Inserts a word into the trie. */
void insert(string word) {
Trie *cur = this; // cur初始化为根结点指针
for (char w : word){ // 遍历word中的每一个字符
if (cur->next[w - 'a'] == NULL){ // 下一个结点不存在,新增一个结点
Trie *new_node = new Trie();
cur->next[w - 'a'] = new_node;
}
cur = cur->next[w - 'a'];
}
cur->is_str = true; // 当前结点已经是一个完整的字符串了
}
/** Returns if the word is in the trie. */
bool search(string word) {
Trie *cur = this;
for (char w : word){
if (cur != NULL)
cur = cur->next[w - 'a']; // 更新cur指针的指向,使其指向下一个结点
}
return (cur != NULL&&cur->is_str);//要判断is_str是否标记为结尾
}
//Returns if there is any word in the trie that starts with the given prefix
bool startsWith(string prefix) {
Trie *cur = this;
for (char w : prefix){
if (cur != NULL)
cur = cur->next[w - 'a'];
}
return (cur != NULL);//判断是不是前缀只要判断是否为null即可
}
};
我们前缀树的代码只有这些,这也是leetcode前缀树的答案,有兴趣的同学戳链接自己可以照着上面的思路补全代码,其他语言的同学也不要着急,最重要的是理解思想,因为前缀树真的用的太少了。
使用场景
不过非要说他的用处,一般使用在海量数据处理中:
- 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
- 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。
- 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词
关于前缀树的讲解就到这里了,如果朋友们想深入了解什么是前缀树和后缀树的话可以点击文章最开始july的链接,文章如果有什么错误和不懂的地方也欢迎大家指出。