数据结构与算法系列----字典树
一:背景
什么是字典树?
Trie树。即字典树。又称单词查找树或键树。是一种树形结构,是一种哈希树的变种。
典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以常常被搜索引擎系统用于文本词频统计。它的长处是:最大限度地降低无谓的字符串比較。查询效率比哈希表高。Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
假如给出一些单词。and,as,at,cn。com,构建以下的字典树:
从上图能够发现:
它有3个基本性质:
1.根节点不包括字符,除根节点外每个节点都仅仅包括一个字符。
2.从根节点到某一节点,路径上经过的字符连接起来,为该节点相应的字符串。
3.每一个节点的全部子节点包括的字符都不同样。
你可能会想。这有什么用?
第一:词频统计
可能有人要说了。词频统计简单啊,一个hash或者一个堆就能够打完收工,但问题来了,假设内存有限呢?还能这么玩吗?所以这里我们就能够用trie树来压缩下空间,由于公共前缀都是用一个节点保存的。
第二: 前缀匹配
就拿上面的图来说吧。假设我想获取全部以"a"开头的字符串,从图中能够非常明显的看到是:and,as,at,假设不用trie树,你该怎么做呢?非常显然朴素的做法时间复杂度为O(N2) 。那么用Trie树就不一样了。它能够做到h。h为你检索单词的长度,能够说这是秒杀的效果。
二:完整代码
#define _CRT_SECURE_NO_DEPRECATE #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 #include<iostream> #define MAX 26//如果字符仅仅出现 abc..k..xyz 26个小写英文字母 using namespace std; struct Node { int num; Node * next[MAX]; Node() { num = 0; for (int i = 0; i < MAX; i++) next[i] = nullptr; } }; class Trie { public: Node *root; Trie() { root = new Node; } void Add(Node *node, const char *ch); int Find(const char *ch); void Delete(Node *node, const char *ch); }; void Trie::Add(Node *node, const char *ch) { int len = strlen(ch); if (len == 0) return; int order = *ch - 'a';//得到字符的位置 if (node->next[order] == nullptr) node->next[order] = new Node; len = strlen(ch + 1); if (len == 0)//说明是一个单词的结尾。须要将该单词出现的次数加一 { node->next[order]->num++; return;//以下的递归能够结束了 } Add(node->next[order], ch + 1);//递归下去 } int Trie::Find(const char *ch) { Node *p = root; while (*ch != '\0') { int order = *ch - 'a'; if (p->next[order] == nullptr) return 0; else { ch++; p = p->next[order]; } } return p->num; } void Trie::Delete(Node *node, const char *ch) { int len = strlen(ch); if (len == 0) return; int order = *ch - 'a'; if (node->next[order] == nullptr) return; len = strlen(ch + 1); if (len == 0 && node->next[order]->num != 0)//找到该单词而且该单词出现次数大于0 { node->next[order]->num--; return; } Delete(node->next[order], ch + 1); } int main() { Trie tree; tree.Add(tree.root, "strawberry"); tree.Add(tree.root, "grandfather"); tree.Add(tree.root, "policeman"); tree.Add(tree.root, "breakfast"); tree.Add(tree.root, "mutton"); tree.Add(tree.root, "bus"); tree.Add(tree.root, "bus"); tree.Add(tree.root, "computer"); int k = tree.Find("bus"); cout << k << endl;//2 tree.Delete(tree.root, "bus"); cout << tree.Find("bus") << endl;//1 return 0; }
三:总结
细心的朋友会发现,上面的代码有缺陷。拿Delete操作来说吧,if (len == 0 && node->next[order]->num != 0)。这句代码事实上是有问题的,由于我没有考虑node->next[order]->num==1的情况,假设等于1。如今运行删除。自减1,num就是0了。也就是这个单词从来没出现,那么表示这个 单词这一条路径都要delete(假设这条路径上不存在其它的单词)。而且root处的指针须要reset。
好了,尽管代码是有缺陷的。可是仅仅要把实现搞懂,该怎样进行完好那就是看个人的需求了。
返回文件夹---->数据结构与算法文件夹
另外推荐读者以下这篇博客: http://blog.csdn.net/v_july_v/article/details/6897097,JULY大牛
图片资源和代码參考:http://www.cnblogs.com/huangxincheng/archive/2012/11/25/2788268.html