字典树(Trie树)实现与应用
一、概述
1、基本概念
字典树,又称为单词查找树,Tire数,是一种树形结构,它是一种哈希树的变种。

2、基本性质
- 根节点不包含字符,除根节点外的每一个子节点都包含一个字符
- 从根节点到某一节点。路径上经过的字符连接起来,就是该节点对应的字符串
- 每个节点的所有子节点包含的字符都不相同
3、应用场景
典型应用是用于统计,排序和保存大量的字符串(不仅限于字符串),经常被搜索引擎系统用于文本词频统计。
4、优点
利用字符串的公共前缀来减少查询时间,最大限度的减少无谓的字符串比较,查询效率比哈希树高。
二、构建过程
1、字典树节点定义
class TrieNode // 字典树节点
{
private int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数
private TrieNode[] son;// 所有的儿子节点
private boolean isEnd;// 是不是最后一个节点
private char val;// 节点的值 TrieNode()
{
num = 1;
son = new TrieNode[SIZE];
isEnd = false;
}
}
2、字典树构造函数
Trie() // 初始化字典树
{
root = new TrieNode();
}
3、建立字典树
// 建立字典树
public void insert(String str) // 在字典树中插入一个单词
{
if (str == null || str.length() == 0)
{
return;
}
TrieNode node = root;
char[] letters = str.toCharArray();//将目标单词转换为字符数组
for (int i = 0, len = str.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null) //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符
{
node.son[pos] = new TrieNode();
node.son[pos].val = letters[i];
}
else //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1
{
node.son[pos].num++;
}
node = node.son[pos];
}
node.isEnd = true;
}
4、在字典树中查找是否完全匹配一个指定的字符串
// 在字典树中查找一个完全匹配的单词.
public boolean has(String str)
{
if(str==null||str.length()==0)
{
return false;
}
TrieNode node=root;
char[]letters=str.toCharArray();
for(int i=0,len=str.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]!=null)
{
node=node.son[pos];
}
else
{
return false;
}
}
//走到这一步,表明可能完全匹配,也可能部分匹配,如果最后一个字符节点为末端节点,则是完全匹配,否则是部分匹配
return node.isEnd;
}
5、前序遍历字典树
// 前序遍历字典树.
public void preTraverse(TrieNode node)
{
if(node!=null)
{
System.out.print(node.val+"-");
for(TrieNode child:node.son)
{
preTraverse(child);
}
}
}
6、计算单词前缀的数量
// 计算单词前缀的数量
public int countPrefix(String prefix)
{
if(prefix==null||prefix.length()==0)
{
return-1;
}
TrieNode node=root;
char[]letters=prefix.toCharArray();
for(int i=0,len=prefix.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]==null)
{
return 0;
}
else
{
node=node.son[pos];
}
}
return node.num;
}
完整代码:
package com.xj.test; public class Trie
{
private int SIZE = 26;
private TrieNode root;// 字典树的根 class TrieNode // 字典树节点
{
private int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数
private TrieNode[] son;// 所有的儿子节点
private boolean isEnd;// 是不是最后一个节点
private char val;// 节点的值 TrieNode()
{
num = 1;
son = new TrieNode[SIZE];
isEnd = false;
}
}
Trie() // 初始化字典树
{
root = new TrieNode();
} // 建立字典树
public void insert(String str) // 在字典树中插入一个单词
{
if (str == null || str.length() == 0)
{
return;
}
TrieNode node = root;
char[] letters = str.toCharArray();//将目标单词转换为字符数组
for (int i = 0, len = str.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null) //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符
{
node.son[pos] = new TrieNode();
node.son[pos].val = letters[i];
}
else //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1
{
node.son[pos].num++;
}
node = node.son[pos];
}
node.isEnd = true;
} // 计算单词前缀的数量
public int countPrefix(String prefix)
{
if(prefix==null||prefix.length()==0)
{
return-1;
}
TrieNode node=root;
char[]letters=prefix.toCharArray();
for(int i=0,len=prefix.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]==null)
{
return 0;
}
else
{
node=node.son[pos];
}
}
return node.num;
} // 打印指定前缀的单词
public String hasPrefix(String prefix)
{
if (prefix == null || prefix.length() == 0)
{
return null;
}
TrieNode node = root;
char[] letters = prefix.toCharArray();
for (int i = 0, len = prefix.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null)
{
return null;
}
else
{
node = node.son[pos];
}
}
preTraverse(node, prefix);
return null;
} // 遍历经过此节点的单词.
public void preTraverse(TrieNode node, String prefix)
{
if (!node.isEnd)
{
for (TrieNode child : node.son)
{
if (child != null)
{
preTraverse(child, prefix + child.val);
}
}
return;
}
System.out.println(prefix);
} // 在字典树中查找一个完全匹配的单词.
public boolean has(String str)
{
if(str==null||str.length()==0)
{
return false;
}
TrieNode node=root;
char[]letters=str.toCharArray();
for(int i=0,len=str.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]!=null)
{
node=node.son[pos];
}
else
{
return false;
}
}
//走到这一步,表明可能完全匹配,可能部分匹配,如果最后一个字符节点为末端节点,则是完全匹配,否则是部分匹配
return node.isEnd;
} // 前序遍历字典树.
public void preTraverse(TrieNode node)
{
if(node!=null)
{
System.out.print(node.val+"-");
for(TrieNode child:node.son)
{
preTraverse(child);
}
}
} public TrieNode getRoot()
{
return this.root;
} public static void main(String[]args)
{
Trie tree=new Trie();
String[]strs= {"banana","band","bee","absolute","acm",};
String[]prefix= {"ba","b","band","abc",};
for(String str:strs)
{
tree.insert(str);
}
System.out.println(tree.has("abc"));
tree.preTraverse(tree.getRoot());
System.out.println();
//tree.printAllWords();
for(String pre:prefix)
{
int num=tree.countPrefix(pre);
System.out.println(pre+"数量:"+num);
}
}
}
执行结果截图:

三、简单应用
下面讲一个简单的应用,问题是这样的:
现在有一个英文字典(每个单词都是由小写的a-z组成),单词量很大,而且还有很多重复的单词。
此外,我们还有一些Document,每个Document包含一些英语单词。下面是问题:
(问题1)请你选择合适的数据结构,将所有的英文单词生成一个字典Dictionary?
(问题2)给定一个单词,判断这个单词是否在字典Dictionary中,如果在单词库中,输出这个单词出现总共出现的次数,否则输出NO?
package com.xj.test; import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map; public class Trie
{
private int SIZE = 26;
private TrieNode root;// 字典树的根 class TrieNode // 字典树节点
{
private int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数
private TrieNode[] son;// 所有的儿子节点
private boolean isEnd;// 是不是最后一个节点
private char val;// 节点的值 TrieNode()
{
num = 1;
son = new TrieNode[SIZE];
isEnd = false;
}
}
Trie() // 初始化字典树
{
root = new TrieNode();
} // 建立字典树
public void insert(String str) // 在字典树中插入一个单词
{
if (str == null || str.length() == 0)
{
return;
}
TrieNode node = root;
char[] letters = str.toCharArray();//将目标单词转换为字符数组
for (int i = 0, len = str.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null) //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符
{
node.son[pos] = new TrieNode();
node.son[pos].val = letters[i];
}
else //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1
{
node.son[pos].num++;
}
node = node.son[pos];
}
node.isEnd = true;
} // 计算单词前缀的数量
public int countPrefix(String prefix)
{
if(prefix==null||prefix.length()==0)
{
return-1;
}
TrieNode node=root;
char[]letters=prefix.toCharArray();
for(int i=0,len=prefix.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]==null)
{
return 0;
}
else
{
node=node.son[pos];
}
}
return node.num;
} // 打印指定前缀的单词
public String hasPrefix(String prefix)
{
if (prefix == null || prefix.length() == 0)
{
return null;
}
TrieNode node = root;
char[] letters = prefix.toCharArray();
for (int i = 0, len = prefix.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null)
{
return null;
}
else
{
node = node.son[pos];
}
}
preTraverse(node, prefix);
return null;
} // 遍历经过此节点的单词.
public void preTraverse(TrieNode node, String prefix)
{
if (!node.isEnd)
{
for (TrieNode child : node.son)
{
if (child != null)
{
preTraverse(child, prefix + child.val);
}
}
return;
}
System.out.println(prefix);
} // 在字典树中查找一个完全匹配的单词.
public boolean has(String str)
{
if(str==null||str.length()==0)
{
return false;
}
TrieNode node=root;
char[]letters=str.toCharArray();
for(int i=0,len=str.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]!=null)
{
node=node.son[pos];
}
else
{
return false;
}
}
//走到这一步,表明可能完全匹配,可能部分匹配,如果最后一个字符节点为末端节点,则是完全匹配,否则是部分匹配
return node.isEnd;
} // 前序遍历字典树.
public void preTraverse(TrieNode node)
{
if(node!=null)
{
System.out.print(node.val+"-");
for(TrieNode child:node.son)
{
preTraverse(child);
}
}
}
public TrieNode getRoot()
{
return this.root;
}
public static void main(String[]args) throws IOException
{
Trie tree=new Trie();
String[] dictionaryData= {"hello","student","computer","sorry","acm","people","experienced","who","reminds","everyday","almost"};
//构建字典
for(String str:dictionaryData)
{
tree.insert(str);
}
String filePath="C:\\Users\\Administrator\\Desktop\\sourceFile.txt";
File file=new File(filePath);
if(file.isFile() && file.exists())
{
InputStreamReader read = new InputStreamReader(new FileInputStream(file));
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt = null;
Map<String,Integer> countMap=new HashMap<String,Integer>();
while((lineTxt = bufferedReader.readLine())!= null)
{
if(tree.has(lineTxt))
{
if(countMap.containsKey(lineTxt))
{
countMap.put(lineTxt, countMap.get(lineTxt)+1);
}
else
{
countMap.put(lineTxt, 1);
}
}
else
{
System.out.println(lineTxt+"不在字典中!");
}
}
for(String s:countMap.keySet())
{
System.out.println(s+"出现的次数"+countMap.get(s));
}
read.close();
}
} }
其中text文件内容为:

程序执行结果为:

四、参考资料
字典树(Trie树)实现与应用的更多相关文章
- 字典树(Trie树)的实现及应用
>>字典树的概念 Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树.与二叉查找树不同,Trie树的 ...
- [POJ] #1002# 487-3279 : 桶排序/字典树(Trie树)/快速排序
一. 题目 487-3279 Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 274040 Accepted: 48891 ...
- Atitit 常见的树形结构 红黑树 二叉树 B树 B+树 Trie树 attilax理解与总结
Atitit 常见的树形结构 红黑树 二叉树 B树 B+树 Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 ...
- 洛谷$P4585\ [FJOI2015]$火星商店问题 线段树+$trie$树
正解:线段树+$trie$树 解题报告: 传送门$QwQ$ $umm$题目有点儿长我先写下题目大意趴$QwQ$,就说有$n$个初始均为空的集合和$m$次操作,每次操作为向某个集合内加入一个数$x$,或 ...
- luoguP6623 [省选联考 2020 A 卷] 树(trie树)
luoguP6623 [省选联考 2020 A 卷] 树(trie树) Luogu 题外话: ...想不出来啥好说的了. 我认识的人基本都切这道题了. 就我只会10分暴力. 我是傻逼. 题解时间 先不 ...
- [转载]字典树(trie树)、后缀树
(1)字典树(Trie树) Trie是个简单但实用的数据结构,通常用于实现字典查询.我们做即时响应用户输入的AJAX搜索框时,就是Trie开始.本质上,Trie是一颗存储多个字符串的树.相邻节点间的边 ...
- Luogu P2922 [USACO08DEC]秘密消息Secret Message 字典树 Trie树
本来想找\(01Trie\)的结果找到了一堆字典树水题...算了算了当水个提交量好了. 直接插入模式串,维护一个\(Trie\)树的子树\(sum\)大小,求解每一个文本串匹配时走过的链上匹配数和终点 ...
- 字典树 trie树 学习
一字典树 字典树,又称单词查找树,Trie树,是一种树形结构,哈希表的一个变种 二.性质 根节点不包含字符,除根节点以外的每一个节点都只包含一个字符: 从根节点到某一节点,路径上经过的字符串连接起 ...
- 【字符串算法】字典树(Trie树)
什么是字典树 基本概念 字典树,又称为单词查找树或Tire树,是一种树形结构,它是一种哈希树的变种,用于存储字符串及其相关信息. 基本性质 1.根节点不包含字符,除根节点外的每一个子节点都包含一个字符 ...
- 字典树 Trie树
什么是Trie树? 形如 其中从根节点到红色节点的路径上的字母所连成的字符串即为一个Trie树上所存的字符串. 比如,这个trie树上有ab,abc,bd,dda这些字符串. 至于怎么构建和查找或添加 ...
随机推荐
- C#==>匿名方法 【转】
http://blog.csdn.net/gishero/article/details/5161826 1,匿名方法 C#为委托提供一种机制,可以为委托定义匿名方法,匿名方法没有名称,编译器会定指定 ...
- 自定义 scrapy 爬虫的 requests
之前使用 scrapy 抓取数据的时候 ,默认是在逻辑中判断是否执行下一次请求 def parse(self): # 获取所有的url,例如获取到urls中 for url in urls: yiel ...
- Android 模拟器检测
参考链接:https://github.com/MindMac/HideAndroidEmulator 从多个方面识别模拟器1.用户习惯:联系人数量.短信数量.相册里面照片数量.安装的应用2.从IME ...
- [Compose] 20. Principled type conversions with Natural Transformations
We learn what a natural transformation is and see the laws it must obey. We will see how a natural t ...
- JAVA本地远程连接linux程序监控状态
环境: 1.本地window 2.程序部署在centos 一,启动访问权限安全守护程序 新建文件:jstatd.all.policy ,注意路径 grant codebase "$JA ...
- Swift 3 新特性和迁移详解
写在前面 Swift 3.0 正式版发布了差不多快一个月了,断断续续的把手上和 Swift 相关的迁移到了Swift 3.0.所以写点小总结. 背景 代码量(4万行) 首先,我是今年年初才开始入手 S ...
- 本博客停止更新,移至purecpp.org
声明:本博客停止更新,迁移至C++开源社区purecpp.org, 欢迎持续关注!
- Oracle 11g RAC客户端使用SCAN IP无法连接问题
Oracle 版本:11.2.0.1.0 客户端:Windows Server 2003/PLSQL Developer Oracle服务器端的ip设置如下: ##公网ip 192.168.135.2 ...
- 几种常用远程通信技术(RPC,Webservice,RMI,JMS)的区别
原文链接:http://blog.csdn.net/shan9liang/article/details/8995023 RPC(Remote Procedure Call Protocol) RPC ...
- CentOS 6.5 Python 2.6.6+Flask 用wsgi方式部署在Apache 2.2.15下
1,安装wsgi Apache模块 easy_install mod_wsgi 2,添加/etc/httpd/conf.d/wsgi.conf LoadModule wsgi_module modul ...