是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
字典树又称为前缀树或Trie树,是处理字符串常见的数据结构。假设组成所有单词的字符仅是“a”~"z",请实现字典树结构,并包含以下四个主要功能:
void insert(String word):添加word,可重复添加。
void delete(String word):删除word,如果word添加过多次,仅删除一次。
boolean search(String word):查询word是否在字典树中。
int prefixNumber(String pre):返回以字符串pre为前缀的单词数量。
思考:
字典树的介绍。字典树是一种树形结构,优点是利用字符串的公共前缀来节约存储空间。
基本性质:
字典树的基本性质如下:
也不需要记,看了实现,很自然的性质就理解了。
每个结点内有一个指针数组,里面有二十六个指针,分别指向二十六个字母。
如果指向某个字母的指针为空,那就是以前没有遇到过这个前缀。
搜索的方法为:
(1) 从根结点开始一次搜索;
(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
(3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
(4) 迭代过程……
(5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。
其他操作类似处理
插入也一样,只是转到某个子树时,没有子树,那就创建一个新节点,然后对应指针指向新节点即可。
我们给出定义就更清楚了:
public static class TrieNode {
public int path; //表示由多少个字符串共用这个节点
public int end;//表示有多少个字符串是以这个节点结尾的
public TrieNode[] map;
//哈希表结构,key代表该节点的一条字符路径,value表示字符路径指向的节点
public TrieNode() {
path = 0;
end = 0;
map = new TrieNode[26];
}
}
path和end都是有用的,接下来会说明
insert:
public static class Trie {
private TrieNode root;//头
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
if (word == null) {
return;
}//空串
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0; //哪条路
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a'; //0~25
if (node.map[index] == null) {
node.map[index] = new TrieNode();
}//创建,继续
node = node.map[index];//指向子树
node.path++;//经过加1
}
node.end++;//本单词个数加1
}
public boolean search(String word) {
if (word == null) {
return false;
}
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.map[index] == null) {
return false;//找不到
}
node = node.map[index];
}
return node.end != 0;//end标记有没有以这个字符为结尾的字符串
}
delete:
public void delete(String word) {
//如果有
if (search(word)) {
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.map[index].path-- == 1) {//path减完之后为0
node.map[index] = null;
return;
}
node = node.map[index];//去子树
}
node.end--;//次数减1
}
}
prefixNumber:
public int prefixNumber(String pre) {
if (pre == null) {
return 0;
}
char[] chs = pre.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.map[index] == null) {
return 0;//找不到
}
node = node.map[index];
}
return node.path;//返回经过的次数即可
}
好处:
1.利用字符串的公共前缀来节约存储空间。
2.最大限度地减少无谓的字符串比较,查询效率比较高。例如:若要查找的字符长度是5,而总共有单词的数目是26^5=11881376,利用trie树,利用5次比较可以从11881376个可能的关键字中检索出指定的关键字,而利用二叉查找树时间复杂度是O( log2n ),所以至少要进行log211881376=23.5次比较。可以看出来利用字典树进行查找速度是比较快的。
应用:
<1.字符串的快速检索
<2.字符串排序
<3.最长公共前缀:abdh和abdi的最长公共前缀是abd,遍历字典树到字母d时,此时这些单词的公共前缀是abd。
<4.自动匹配前缀显示后缀
我们使用辞典或者是搜索引擎的时候,输入appl,后面会自动显示一堆前缀是appl的东东吧。
那么有可能是通过字典树实现的,前面也说了字典树可以找到公共前缀,我们只需要把剩余的后缀遍历显示出来即可。
相关题目:
一个字符串类型的数组arr1,另一个字符串类型的数组arr2。
arr2中有哪些字符,是arr1中出现的?请打印。
arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印。
arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印arr2中出现次数最大的前缀。