字典树(Trie树)
字典树,又称单词查找树,Trie树,是一种树形结构,典型应用是用于统计,排序和保存大量的字符串,所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度的减少无谓的字符串比较,查询效率比哈希表高。
它有三个基本性质,根节点不包含字符,除根节点外每一个节点都只包含一个字符,从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串,每个节点的所有子节点包含的字符都不相同。
字典树的插入,删除和查找都非常简单,用一个一重循环即可。
1. 从根节点开始一次搜索
2. 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索
3. 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索
4. 迭代上述过程…
5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,即完成查找
字典树的应用
1.字典树在串的快速检索中的应用。
给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。
字典树在“串”排序方面的应用
给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出
用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可字典树在最长公共前缀问题的应用
对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为最近公共祖先问题。
字典树的基本功能是用来查询某个单词(前缀)在所有单词中出现次数的一种数据结构,它的插入和查询复杂度都为O(len),Len为单词(前缀)长度,但是它的空间复杂度却非常高,如果字符集是26个字母,那每个节点的度就有26个,典型的以空间换时间结构。
上面的是复制来的
(原文:http://www.cnblogs.com/pony1993/archive/2012/07/18/2596730.html)
一直以来只是听过字典树这种数据结构,并没有实际编码实现过,这次总算认认真真看了一下, 并不是特别难理解,考虑考虑就明白了。
上面复制的部分已经把TrieTree的特点总结的比较清楚了,下面是一份简单的代码
题目是LeetCode208
实现字典树(TrieTree)
#include<iostream>
#include<cstring>
using namespace std;
class TrieNode {
public:
char c;
TrieNode* next[26];
bool isWord;
// Initialize your data structure here.
TrieNode():isWord(false){
memset(next,0,sizeof(TrieNode*) * 26);
}
TrieNode(char _c):c(_c),isWord(false){
memset(next,0,sizeof(TrieNode*) * 26);
}
};
class Trie {
public:
Trie() {
root = new TrieNode();
}
// Inserts a word into the trie.
void insert(string word) {
TrieNode* p = root;
for(int i = 0;i < word.size(); ++i){
int idx = word[i] - 'a';
if(!p -> next[idx]){
p -> next[idx] = new TrieNode(word[i]);
}
p = p->next[idx];
}
p->isWord = true;
}
// Returns if the word is in the trie.
bool search(string word) {
TrieNode* p = root;
for(int i = 0; i < word.size(); ++i){
int idx = word[i]-'a';
if(p -> next[idx]){
p = p->next[idx];
}else{
return false;
}
}
return p->isWord;
}
// Returns if there is any word in the trie
// that starts with the given prefix.
bool startsWith(string prefix) {
TrieNode* p = root;
for(int i = 0; i < prefix.size(); ++i){
int idx = prefix[i] - 'a';
if(!p->next[idx]) return false;
p = p->next[idx];
}
return true;
}
private:
TrieNode* root;
};
// Your Trie object will be instantiated and called as such:
// Trie trie;
// trie.insert("somestring");
// trie.search("key");
int main(){
Trie trie;
trie.insert("abcd");
trie.insert("cdef");
trie.insert("aase");
bool t = trie.startsWith("abc");
cout<<t<<endl; //true
t = trie.search("abcd");
cout<<t<<endl;
}
解释一下,每个节点都保存有大小与字典中字符数量相等的指针数组,代码中的next[26],26是因为字典中的字符数量是26个(只包含小写),然后进行相应的操作——插入(建树),查找等等,都比较基础。