字典树,又称单词查找树,Trie树,是一种树形结构,典型应用是用于统计,排序和保存大量的字符串,所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度的减少无谓的字符串比较,查询效率比哈希表高。
它有三个基本性质,根节点不包含字符,除根节点外每一个节点都只包含一个字符,从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串,每个节点的所有子节点包含的字符都不相同。

字典树的插入,删除和查找都非常简单,用一个一重循环即可。
1. 从根节点开始一次搜索
2. 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索
3. 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索
4. 迭代过程...
5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,即完成查找
字典树的应用
1.字典树在串的快速检索中的应用。
给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。
2. 字典树在“串”排序方面的应用
给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出
用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可
3. 字典树在最长公共前缀问题的应用
对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为最近公共祖先问题。
1. 从根节点开始一次搜索
2. 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索
3. 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索
4. 迭代过程...
5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,即完成查找
字典树的应用
1.字典树在串的快速检索中的应用。
给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。
2. 字典树在“串”排序方面的应用
给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出
用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可
3. 字典树在最长公共前缀问题的应用
对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为最近公共祖先问题。
字典树的基本功能是用来查询某个单词(前缀)在所有单词中出现次数的一种数据结构,它的插入和查询复杂度都为O(len),Len为单词(前缀)长度,但是它的空间复杂度却非常高,如果字符集是26个字母,那每个节点的度就有26个,典型的以空间换时间结构。
java代码实现:
public class Trie {
/**
* @param args
*/
//各个节点的子树数目即字符串中的字符出现的最多种类
private final int SIZE = 26;
//除根节点外其他所有子节点的数目
private int numNode;
//树的深度即最长字符串的长度
private int depth;
//字典树的根
private TrieNode root;
public Trie() {
this.numNode=0;
this.depth=0;
this.root = new TrieNode();
}
private class TrieNode {
// 所有的儿子节点或者一级子节点
private TrieNode[] son;
// 有多少字符经过或到达这个节点,即节点字符出现的次数
private int numPass;
// 有多少字符串通过这个节点并到此结束的数量
private int numEnd;
// 是否有结束节点
private boolean isEnd;
// 节点的值
private char value;
/**
* 初始化节点类
*/
public TrieNode() {
this.numPass=0;
this.numEnd=0;
this.son = new TrieNode[SIZE];
this.isEnd = false;
}
}
/**
* 对操作的字符串进行一个非法的判断,是否为字母构成的字符串
*/
private boolean isStrOfLetter(String str){
//null或者空白字符串,则插入失败
if (str==null){
return false;
}
return true;
}
/**
* 插入方法,插入字符串,不区分大小写
*/
public boolean insertStr(String str){
if(!isStrOfLetter(str)){
return false;
}
//插入字符 不区分大小写 全部转换成小写
str=str.toLowerCase();
char letters[]=str.toCharArray();
//从父节点开始
TrieNode node=this.root;
for(char c:letters){
int pos=c-'a';
if(node.son[pos]==null){//字符不存在
node.son[pos]=new TrieNode();
node.son[pos].value=c;
node.son[pos].numPass=1;
this.numNode++;
}else{
node.son[pos].numPass++;
}
//为下一个字符做准备
node=node.son[pos];
}
node.isEnd = true;//标记:有字符串到了此节点已结束
node.numEnd++;//这个字符串重复次数
if(letters.length>this.depth){//记录树的深度
this.depth=letters.length;
}
//插入成功
return true;
}
/**
* 在字典树中查找是否存在某字符串为前缀开头的字符串(包括前缀字符串本身),不区分大小写
*/
public boolean isContainPrefix(String str){
//查找的字符是否非法字符,则失败
if(!isStrOfLetter(str)){
return false;
}
//查找字符串
str=str.toLowerCase();//不区分大小写,转为小写
char[] letters = str.toCharArray();//转成字符数组
TrieNode node=this.root;//先从父节点开始
for(char c:letters){
int pos=c-'a';
if(node.son[pos]!=null){
node=node.son[pos];//在此字符串下继续查找
}else{
return false;
}
}
return true;
}
/**
* 在字典树中查找是否存在某字符串(不为前缀),不区分大小写
*/
public boolean isContainStr(String str){
//查找的字符是否非法字符,则失败
if(!isStrOfLetter(str)){
return false;
}
//查找字符串
str=str.toLowerCase();//不区分大小写,转为小写
char[] letters = str.toCharArray();//转成字符数组
TrieNode node=this.root;//先从父节点开始
for(char c:letters){
int pos=c-'a';
if(node.son[pos] != null){
node=node.son[pos];//此字符存在继续查找下一个字符
}else{
return false;
}
}
return node.isEnd;
}
/**
* 统计以指定字符串为前缀的字符串数量,不区分大小写
*/
public int countPrefix(String str) {
//统计的字符是否非法字符,则返回0
if(!isStrOfLetter(str)){
return 0;
}
//查找字符串
str=str.toLowerCase();//不区分大小写,转为小写
char[] letters = str.toCharArray();//转成字符数组
TrieNode node=this.root;//先从父节点开始
for (char c:letters) {
int pos = c - 'a';//得到应存son[]中的索引
if (node.son[pos] == null) {
return 0;//没有以此字符串为前缀开头
} else {//此字符存在,继续遍历
node=node.son[pos];
}
}
return node.numPass;
}
/**
* 统计以指定字符串为前缀的字符串数量,不区分大小写
*/
public int countPrefix1(String str) {
//统计的字符是否非法字符,则返回0
if(!isStrOfLetter(str)){
return 0;
}
//查找字符串
str=str.toLowerCase();//不区分大小写,转为小写
char[] letters = str.toCharArray();//转成字符数组
TrieNode node=this.root;//先从父节点开始
for (char c:letters) {
int pos = c - 'a';//得到应存son[]中的索引
if (node.son[pos] == null) {
return 0;//没有以此字符串为前缀开头
} else {//此字符存在,继续遍历
node=node.son[pos];
}
}
return node.numPass;
}
public int countStr(String str) {
//统计的字符是否非法字符,则返回0
if(!isStrOfLetter(str)){
return 0;
}
//查找字符串
str=str.toLowerCase();//不区分大小写,转为小写
char[] letters = str.toCharArray();//转成字符数组
TrieNode node=this.root;//先从父节点开始
for (char c:letters) {
int pos = c - 'a';//得到应存son[]中的索引
if (node.son[pos] == null) {
return 0;//没有以此字符串为前缀开头
} else {//此字符存在,继续遍历
node=node.son[pos];
}
}
return node.numEnd;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一个字典树(其实可以在创建时指定字典树各节点的大小,大小根据存入字符种类的数量)
Trie trie=new Trie();
//测试字符串(当然越庞大越能展现它的优势)
String[] testStrs=new String[]{"chefsd","chen","hahi","ch","cxing","hahha","my","home"};
for(String s:testStrs){//向字典树中存入字符串
trie.insertStr(s);
}
//测试是否包含指定前缀的字符串
boolean isCont=trie.isContainPrefix("ch");
System.out.println(isCont);//输出true
//测试包含指定前缀的字符串的数量
int countPrefix=trie.countPrefix1("ch");
System.out.println(countPrefix);//输出3
//测试包含指定字符串的数量
int countStr=trie.countStr("ch");
System.out.println(countStr);//输出1
//测试包含指定前缀的字符串的数量
int countPre=trie.countPrefix("chee");
System.out.println(countPre);//输出0
}
}