数据结构《16》----自动补齐实现《一》----Trie 树
2014-04-25 22:47
387 查看
1. 简述
Trie 树是一种高效的字符串查找的数据结构。可用于搜索引擎中词频统计,自动补齐等。
在一个Trie 树中插入、查找某个单词的时间复杂度是 O(len), len是单词的长度。
如果采用平衡二叉树来存储的话,时间复杂度是 O(lgN), N为树中单词的总数。
此外,Trie 树还特别擅长 前缀搜索,比方说现在输入法中的自动补齐,输入某个单词的前缀,abs,
立刻弹出 abstract 等单词。
Trie 树优良的查找性能是建立在 牺牲空间复杂度的基础之上的。
本文将给出一个 Trie树的简单实例,并用这个Trie建立了一个单词数目是 7000+的英语词典。
从而分析 Trie 树所占的空间。
2. 定义
一棵典型的 Trie 树,如下图所示:
每一个节点包含一个长度是 26 的指针数组。这 26 个指针分别代表英文 26 个字母。
同时,每个节点拥有一个红色标记,表示 root 到当前的路径是否是一个单词。
例: 下图中最左边的一个路径表示单词 abc 和 abcd.
3. 性能
本人做了一个小测试,当建立一个 7000+ 的词典时,Trie 树共分配了 22383 个节点,每个节点占了 27 * 4 BYTE,
所以共消耗了大约 22383 * 27 * 4 BYTE = 2.4 M
而这 7000 个单词平均长度假设是 8 个字母,那么总共占 7000 * 8 BYTE= 5.6 KB
两者相差 42 倍!!!
从上述小测试可以看到,Trie 树需要占用大量的空间,特别是如果考虑大小写,或者建立汉字的 Trie树时,每个节点所需要的指针数目将更大。
其实,大伙一眼就能发现,Trie 树中,每个节点包含了大量的空指针,因而造成了大量的空间消耗。
可以采用 三叉树(Ternary Search Tree), 改进 Trie 树。将在下一篇文章中讨论。
4. 源码
Trie 树是一种高效的字符串查找的数据结构。可用于搜索引擎中词频统计,自动补齐等。
在一个Trie 树中插入、查找某个单词的时间复杂度是 O(len), len是单词的长度。
如果采用平衡二叉树来存储的话,时间复杂度是 O(lgN), N为树中单词的总数。
此外,Trie 树还特别擅长 前缀搜索,比方说现在输入法中的自动补齐,输入某个单词的前缀,abs,
立刻弹出 abstract 等单词。
Trie 树优良的查找性能是建立在 牺牲空间复杂度的基础之上的。
本文将给出一个 Trie树的简单实例,并用这个Trie建立了一个单词数目是 7000+的英语词典。
从而分析 Trie 树所占的空间。
2. 定义
一棵典型的 Trie 树,如下图所示:
每一个节点包含一个长度是 26 的指针数组。这 26 个指针分别代表英文 26 个字母。
同时,每个节点拥有一个红色标记,表示 root 到当前的路径是否是一个单词。
例: 下图中最左边的一个路径表示单词 abc 和 abcd.
3. 性能
本人做了一个小测试,当建立一个 7000+ 的词典时,Trie 树共分配了 22383 个节点,每个节点占了 27 * 4 BYTE,
所以共消耗了大约 22383 * 27 * 4 BYTE = 2.4 M
而这 7000 个单词平均长度假设是 8 个字母,那么总共占 7000 * 8 BYTE= 5.6 KB
两者相差 42 倍!!!
从上述小测试可以看到,Trie 树需要占用大量的空间,特别是如果考虑大小写,或者建立汉字的 Trie树时,每个节点所需要的指针数目将更大。
其实,大伙一眼就能发现,Trie 树中,每个节点包含了大量的空指针,因而造成了大量的空间消耗。
可以采用 三叉树(Ternary Search Tree), 改进 Trie 树。将在下一篇文章中讨论。
4. 源码
// Last Update:2014-04-16 23:24:47 /** * @file trie.h * @brief Trie * @author shoulinjun@126.com * @version 0.1.00 * @date 2014-04-16 */ #ifndef TRIE_H #define TRIE_H #include <iostream> #include <fstream> #include <string> #include <cstring> using std::string; using std::cout; using std::endl; const int branchNum = 26; struct TrieNode { TrieNode(): isStr(false) { memset(next, 0, sizeof(next)); } bool isStr; TrieNode* next[branchNum]; }; string ToLower(const string &s) { string str; string::const_iterator it = s.begin(); while(it != s.end()) { str += (char)tolower(*it); ++ it; } return str; } /** * a simple data stucture * usefull for AutoComplete */ class Trie { public: Trie(): root(new TrieNode()) {} ~Trie() { cout << "# of nodes allocated: " << count << endl; destroy(root); } void Insert(const string &str); bool Search(const string &str) const; void AutoComplete(const string &str); void Input(const string &file); private: TrieNode* find(const string &str) const; void dfs(TrieNode *root, string &path); void destroy(TrieNode * &root); TrieNode *root; static size_t count; }; size_t Trie::count = 0; void Trie::destroy(TrieNode * &root) { for(int i=0; i<branchNum; ++i) { if(root->next[i]) destroy(root->next[i]); } delete root; root = NULL; } void Trie::Insert(const string &s) { if(s.empty()) return; /* support lower cases now */ string str = ToLower(s); string::const_iterator it = str.begin(); TrieNode *location(root); // bypassing existing nodes while(it != str.end() && location->next[*it - 'a'] != NULL) { location = location->next[*it - 'a']; ++ it; } // Insert while(it != str.end() && location->next[*it - 'a'] == NULL) { location->next[*it - 'a'] = new TrieNode(); ++ count; location = location->next[*it - 'a']; ++ it; } location->isStr = true; } void Trie::Input(const string &str) { std::ifstream ifile(str.c_str()); string word; while(ifile >> word) { Insert(word); } ifile.close(); } bool Trie::Search(const string &s) const { TrieNode *location = root; string str = ToLower(s); location = find(str); return (location) && location->isStr; } TrieNode* Trie::find(const string &str) const { TrieNode *location = root; string::const_iterator it = str.begin(); while(it != str.end() && location->next[*it - 'a'] != NULL) { location = location->next[*it - 'a']; ++ it; } return (it == str.end()) ? location : NULL; } void Trie::dfs(TrieNode *root, string &path) { if(root == NULL) return; if(root->isStr) cout << path << endl; for(char x='a'; x<='z'; ++x) { if(root->next[x-'a'] != NULL) { path += x; dfs(root->next[x-'a'], path); path.resize(path.size()-1); } } } void Trie::AutoComplete(const string &str) { TrieNode *location(root); string path; location = find(str); path = str; dfs(location, path); } #endif /*TRIE_H*/
相关文章推荐
- 线性表(二)
- 《算法与数据结构》实验课实验三思考题——有序链表插入元素——C语言
- redis 存储数据结构
- 数据结构之排序
- 链表的基本操作
- 2013级数据结构第三次上机解题报告
- 迷宫问题递归与非递归求解
- 线性表(一)
- 《数据结构教程》(李春葆 主编)课后习题【练习题5】
- redis的源码分析之不同编码类型的数据结构
- 数据结构--栈(基础知识)
- (三)数据结构
- 数据结构 - 快速排序(Quick Sort) 详解 及 代码(C++)
- javascript实现数据结构:串--堆分配存储表示
- javascript实现数据结构:串--定长顺序存储表示以及kmp算法实现
- HDOJ 1075 What Are You Talking About
- 数据结构——树的集合
- Nginx学习(3)—封装的数据结构
- 神奇的魔术方阵
- 海量数据处理相关算法及数据结构【转】