前言

Trie树又称单词查找树,字典树,是哈希树的变种;

优点在于:最大限度地减少无谓的字符串比较,查询效率比哈希高;

缺点在于:空间消耗很大;

性质

其基本性质可以归纳为:

  1. 跟结点不包括字符,除跟结点以外,每个结点只包含一个字符;
  2. 从跟结点到某一个结点,路径上经过的字符连接起来,为该结点对应的字符串;
  3. 每个结点的所有子结点包含的字符串不相同;

其中结点对儿子的指向,有三种方法:

  1. 对每个结点开字母集大小的数组,对应的下标是儿子所表示的字母,内容则是这个儿子对应在大数组上的位置,即标号;
  2. 对每个结点连一个链表,按一定顺序记录每个儿子;
  3. 使用树的左儿子右兄弟表示法

第一种方法容易实现,但实际的空间要求比较大,第二种,较易实现,空间要求相对较小,但比较费时,第三种,空间要求最小,但相对费时而且不易写;

示意图


代码:

const int MAX_CHARS = 26;

class TrieNode {
public:
TrieNode(string s) : isWord(false), word(s) {
memset(children, 0, sizeof(children));
} string word;
bool isWord; // 标记该结点是否构成单词
TrieNode* children[MAX_CHARS]; // 子树
}; class TrieTree {
public:
TrieTree():root(new TrieNode("")) {}
~TrieTree() { freeTree(root); } TrieNode * getRoot() {
return root;
} void addWord(string & s) {
TrieNode * node = root;
string t;
for (int i = 0; i < s.size(); ++i) {
t += s[i];
if (node->children[s[i] - 'a'] == NULL) {
node->children[s[i] - 'a'] = new TrieNode(t);
}
node = node->children[s[i] - 'a'];
}
node->isWord = true;
} private:
TrieNode * root;
void freeTree(TrieNode* node) {
for (int i = 0; i < MAX_CHARS; ++i) {
if (node->children[i] != NULL) {
freeTree(node->children[i]);
}
}
delete node;
}
};

例题

以例题 Word Search ||,从字符数组中连接字符并查找有无符合字符串数组的字符串;

贴上代码:

/// 使用Trie树进行
class TrieNode {
public:
TrieNode(string s) : isWord(false), word(s) {
memset(children, 0, sizeof(children));
} string word;
bool isWord; // 是否到达终点
TrieNode* children[MAX_CHARS];
}; class TrieTree {
public:
TrieTree():root(new TrieNode("")) {}
~TrieTree() { freeTree(root); } TrieNode * getRoot() {
return root;
} void addWord(string & s) {
TrieNode * node = root;
string t;
for (int i = 0; i < s.size(); ++i) {
t += s[i];
if (node->children[s[i] - 'a'] == NULL) {
node->children[s[i] - 'a'] = new TrieNode(t);
}
node = node->children[s[i] - 'a'];
}
node->isWord = true;
} private:
TrieNode * root;
void freeTree(TrieNode* node) {
for (int i = 0; i < MAX_CHARS; ++i) {
if (node->children[i] != NULL) {
freeTree(node->children[i]);
}
}
delete node;
}
}; void helper2(vector<vector<char>> & board, TrieNode* root, vector<string> & result, int i, int j) {
if (i < 0 || j < 0 || i >= board.size() || j >= board[0].size() || board[i][j] == '*')
return;
char ch = board[i][j];
root = root->children[ch - 'a'];
if (root == NULL) return;
if (root->isWord) {
result.push_back(root->word);
root->isWord = false;
}
board[i][j] = '*';
helper2(board, root, result, i - 1, j);
helper2(board, root, result, i + 1, j);
helper2(board, root, result, i, j - 1);
helper2(board, root, result, i, j + 1);
board[i][j] = ch;
}
vector<string> findWords3(vector<vector<char>>& board, vector<string>& words) {
TrieTree t;
for (int i = 0; i < words.size(); ++i) {
t.addWord(words[i]);
} vector<string> result;
for (int i = 0; i < board.size(); ++i) {
for (int j = 0; j < board[i].size(); ++j) {
helper2(board, t.getRoot(), result, i, j);
}
} return result;
} bool test() {
vector<vector<char>> board1 = {
{'o','a','a','n'},
{'e','t','a','e'},
{'i','h','k','r'},
{'i','f','l','v'}
};
vector<string> words1 = {"oath","pea","eat","rain"}; vector<vector<char>> board2 = {
{"a", "a"}
};
vector<string> words2 = {"aaa"}; vector<string> result = findWords3(board1, words1); cout << result.size() << endl; return true;
}

分析

在trie树中查找一个关键字的时间和树中包含的结点数目无关,而是取决于组成关键字的字符数。而二叉查找树的查找时间的树中的结点数目有关,其时间复杂度为O(log2n)。

如果要查找的关键字可以分解成字母序列并且不是很长,利用trie树查找速度优于二叉查找 树。比如若关键字长度最大是5,则利用trie树,利用5次比较可以从26^5=11881376个可能的关键字中检索出指定的关键字。而利用二叉查找树至少要进行次比较。

应用

  1. 字符串的检索,词频统计,搜索引擎的热门查询;

    像是一些大数据的问题,像是:
  • 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
  • 给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
  • 给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。
  • 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串
  • 寻找热门查询:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。
  1. 字符串的最长公共前缀;

    举例:
  • 给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少. 解决方案:

    首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线 (Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。

    而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:

    1. 利用并查集(Disjoint Set),可以采用采用经典的Tarjan 算法;

    2. 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;
  1. 排序;

    trie树是一颗多叉树,只需要先序遍历整棵树,输出相应的字符串便是字典序的结果。

  2. 作为其他数据结构和算法的辅助结构;

    像是后缀树,AC自动机等;

这些问题,我打算在后面专门来做总结;

高级实现

双数组实现(后面会有专门的文章记录实现)

Trie树理解的更多相关文章

  1. Trie树 理解

    Trie树的理解 Trie树又称单词查找树,字典树,是哈希树的变种: 优点在于:最大限度地减少无谓的字符串比较,查询效率比哈希高: 缺点在于:空间消耗很大: 性质 其基本性质可以归纳为: 跟结点不包括 ...

  2. Trie树之C-实现

    title: Trie树之C++实现 comments: true date: 2016-10-02 16:59:54 categories: 算法 tags: Trie树 前言 之前写了一篇偏向于理 ...

  3. Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结

    Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 ...

  4. 【动画】看动画轻松理解「Trie树」

    Trie树 Trie这个名字取自“retrieval”,检索,因为Trie可以只用一个前缀便可以在一部字典中找到想要的单词. 虽然发音与「Tree」一致,但为了将这种 字典树 与 普通二叉树 以示区别 ...

  5. Trie树【P3879】 [TJOI2010]阅读理解

    Description 英语老师留了N篇阅读理解作业,但是每篇英文短文都有很多生词需要查字典,为了节约时间,现在要做个统计,算一算某些生词都在哪几篇短文中出现过. Input 第一行为整数N,表示短文 ...

  6. 13-看图理解数据结构与算法系列(Trie树)

    Trie树 Trie树,是一种搜索树,也称字典树或单词查找树,此外也称前缀树,因为某节点的后代存在共同的前缀.它的key都为字符串,能做到高效查询和插入,时间复杂度为O(k),k为字符串长度,缺点是如 ...

  7. 【BZOJ-4523】路由表 Trie树 + 乱搞

    4523: [Cqoi2016]路由表 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 155  Solved: 98[Submit][Status][ ...

  8. [知识点]Trie树和AC自动机

    // 此博文为迁移而来,写于2015年5月27日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w1s8.html 1.前 ...

  9. 字符串 --- KMP Eentend-Kmp 自动机 trie图 trie树 后缀树 后缀数组

    涉及到字符串的问题,无外乎这样一些算法和数据结构:自动机 KMP算法 Extend-KMP 后缀树 后缀数组 trie树 trie图及其应用.当然这些都是比较高级的数据结构和算法,而这里面最常用和最熟 ...

随机推荐

  1. NB-iot 和 emtc两种技术区别

    此前有报道称,工信部正在拟定推动窄频物联网(NB-IoT)标准化,并对NB-IoT模块外形.封装以及针脚定义等提出新规范.业内人士认为,标准出台后将促进物联网规模化商用全面提速,迎来行业成长爆发期. ...

  2. 四、Springboot Debug调试

    描述: 在使用maven插件执行spring-boot:run进行启动的时候,如果设置的断点进不去,要进行以下的设置. 1.添加jvm参数配置 在spring-boot的maven插件加上jvmArg ...

  3. git —— Feature分支

    添加新功能时,新建feature分支 分支上开发完成后,再进行合并.最后删除feature分支 $ git checkout -b feature-vulcan 开发完毕后,切换回添加的分支,进行合并 ...

  4. dede图集内容页调用

    {dede:productimagelist} <li> <img src="[field:imgsrc/]" width="92" heig ...

  5. HDU 1669 Jamie's Contact Groups(多重匹配+二分枚举)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1669 题目大意: 给你各个人可以属于的组,把这些人分组,使这些组中人数最多的组人数最少,并输出这个人数 ...

  6. windows 依赖查看

    使用工具Download Process Explorer查看运行程序所依赖的动态库. 中文说明:适用于 Windows 的 Process Explorer 10.21 版

  7. Nginx 虚拟主机 VirtualHost 配置

    Nginx 是一个轻量级高性能的 Web 服务器, 并发处理能力强, 对资源消耗小, 无论是静态服务器还是小网站, Nginx 表现更加出色, 作为 Apache 的补充和替代使用率越来越高. 我在& ...

  8. Monaco Editor 使用入门

    以前项目是用ace编辑器的,但是总有些不敬人意的地方.前端事件看见的VS Code编辑器Monaco Editor准备更换下,下面介绍一些使用中遇到的一点问题.代码提示 1.项目引用 import * ...

  9. Asp.net Vnext 调试源码

    概述 本文已经同步到<Asp.net Vnext 系列教程 >中] 如果想对 vnext深入了解,就目前为止太该只有调试源码了 实现 github上下载源码 选择对应的版本,版本错了是不行 ...

  10. python开发学习-day14(jquery、ajax等)

    s12-20160421-day14 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: ...