1. 概述
1)定义:字典树,又叫单词查找树,Trie树,他是哈希树的变种,是一种N叉树,一种特色的前缀树结构。看下图:
哈希树:可参考查找——图文详解HashTree(哈希树)
哈希树可以广泛应用于那些需要对大容量数据进行快速匹配操作的地方。例如:数据库索引系统、短信息中的收条匹配、大量号码路由匹配、信息过滤匹配。哈希树不需要额外的平衡和防止退化的操作,效率十分理想。
前缀树:前缀树是N叉树的一种特殊形式。通常来说,一个前缀树是用来存储字符串的。前缀树的每个节点代表一个字符串(前缀)。每个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的原始字符串,以及通往该子节点路径上所有的字符组成的。前缀树的一个特点是:节点所有孩子与该节点相关的字符串有着共同前缀。
2)性质:
- 根节点不包含字符,除外每个节点都只包含一个字符;
- 从根节点到某一节点,路径上经过的字符串联是该节点对应的字符串;
- 每个节点的所有子节点包含字符不同。
2. 各种构造及实现
搜索字典的方法:
package Test;
/**
* @author hehuan
* @date 2020年4月6日下午4:10:11
*/
public class Trie {
private static final int SIZE=26;
private TrieNode root;//字典树的根
//字典树节点定义
class TrieNode{
private int num;//通过该节点的单词个数(由根节点到该节点组成的字符串出现的次数)
private TrieNode[] childNodes;//孩子节点
private boolean isEnd;//是否是最后一个节点
private char val;//节点的值
public TrieNode() {
num=1;
childNodes = new TrieNode[SIZE];
isEnd=false;
}
}
//初始化字典树
public 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; i < letters.length; i++) {
int pos=letters[i]-'a';
if (node.childNodes[pos]==null) {
//如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并赋值该字符
node.childNodes[pos]=new TrieNode();
node.childNodes[pos].val=letters[i];
}else {
//如果出现了该字符,则将由根到该节点孩子节点字符串模式出现次数+1
node.childNodes[pos].num++;
}
node = node.childNodes[pos];
}
node.isEnd=true;
}
//在字典树中查找一个完全匹配的单词
public boolean find(String str) {
if (str==null||str.length()==0) return false;
TrieNode node=root;
char[] letters=str.toCharArray();
for (int i = 0; i < letters.length; i++) {
int pos = letters[i]-'a';
if (node.childNodes[pos]!=null) {
node=node.childNodes[pos];
}else {
return false;
}
}
//如果最后一个节点能走到底,则表示是完全匹配,如果不是最后一个节点走到底,就是部分匹配
return node.isEnd;
}
//前序遍历字典树
public void preTraverse(TrieNode node) {
if (node!=null) {
System.out.print(node.val+"-");
for (TrieNode child:node.childNodes) {
preTraverse(child);
}
}
}
//计算前缀单词的数量
public int PrefixNum(String prefix) {
if (prefix==null||prefix.length()==0) return -1;
TrieNode node = root;
char[] letters = prefix.toCharArray();
for (int i = 0; i < letters.length; i++) {
int pos = letters[i]-'a';
if (node.childNodes[pos]!=null) {
node=node.childNodes[pos];
}else {
return 0;
}
}
return node.num;
}
//遍历经过该节点的单词
public void preTraverse(TrieNode node,String prefix) {
if (!node.isEnd) {
for (TrieNode child:node.childNodes) {
if (child!=null) {
preTraverse(node, prefix+child.val);
}
}
return;
}
System.out.println(prefix);
}
//打印指定前缀的单词
public String PrefixPrint(String prefix) {
if (prefix==null||prefix.length()==0) return null;
TrieNode node=root;
char[] letters = prefix.toCharArray();
for (int i = 0; i < letters.length; i++) {
int pos = letters[i]-'a';
if (node.childNodes[pos]!=null) {
node=node.childNodes[pos];
}else {
return null;
}
}
preTraverse(node,prefix);
return null;
}
//获得根节点
public TrieNode getRoot() {
return this.root;
}
public static void main(String[] args) {
Trie trie = new Trie();
String[] strs = {"hello","hi","wor","world","hello","he","huan","hehuan"};
String[] prefix = {"h","he","wo"};
for (String str:strs) {
trie.insert(str);
}
System.out.println(trie.find("wo"));
System.out.println(trie.find("world"));
trie.preTraverse(trie.getRoot());
System.out.println();
for (String pre:prefix) {
int num=trie.PrefixNum(pre);
System.out.println(pre+"数量:"+num);
}
}
}
运行结果:
false
true
-h-e-h-u-a-n-l-l-o-i-u-a-n-w-o-r-l-d-
h数量:6
he数量:4
wo数量:2
3.应用场景
- 有一个很大很大文本(全是单词组成),查找出现次数最多的单词?
- 判断一个单词是否在这个超大文本中出现?(单词检索)
给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。 - 查找每个单词在文本中出现的次数?(词频统计)
- 对文本中的单词排序?(先序遍历)
给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出
用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序,对这棵树进行先序遍历即可。 - 查找单词最长公共前缀?
对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为当时公共祖先问题。