一)Trie字典树简介
Trie字典树又可以称为前缀树。
典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
优点:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
二)Trie字典树模型
该模型就没有用具体的单词创建了,随便定义了一些错误单词。
String[] words = {"abcd", "A", "Accd", "ab", "B", "ccc", "abe", "abf", "abhh"};
备注:橘黄色的节点表示一个完整的单词尾部。
三)Trie字典树
原理:利用Map中的TreeMap存储字典树的节点信息。
第一步:定义Trie字典树结构
import java.util.Map;
import java.util.TreeMap;
/**
* Trie字典树
* @author ouyangjun
*/
public class TrieTree {
// 字典类
static class Node {
boolean isWord; // 标识是否属于一个完整的词语
Character key; // 节点的值, 作为子节点的key
int prefixNum; // 表示有多少个词, 以此单词为前缀
Map<Character, Node> next; // 节点的所有子节点,用Map来保存 (Map next)
Node() {
next = new TreeMap<Character, Node>();
}
Node(boolean isWord, Character key, int prefixNum) {
this();
this.isWord = isWord;
this.key = key;
this.prefixNum = prefixNum;
}
}
private Node root; // 根节点
private int size; // 节点数量
// 初始化
public TrieTree() {
root = new Node();
}
// 获取根节点
public Node getRoot() {
return root;
}
// 获取节点数量
public int size() {
return size;
}
// 判断节点是否为空
public boolean isEmpty() {
return size()==0;
}
}
第二步:新增词语
说明:先把单词转换成字节数组,再判断每一个字符是否已经存在,如不存在,则属于新增的节点,如字符已存在,则不处理。
// 新增节点
public void add(String word) {
if (word == null || word.length()==0) {
return;
}
Node node = root;
char[] ch = word.toCharArray(); // 转换成char
for (char c : ch) {
// 判断节点是否存在
Node child = node.next.get(c);
if (child == null) {
// 新增节点
node.next.put(c, new Node(false, c, 0));
}
node.next.get(c).prefixNum++;
node = node.next.get(c); // 查找下一个子节点
}
// 如果当前的node已经是一个word,则不需要添加
if (!node.isWord) {
size++;
node.isWord = true;
}
}
第三步:查找词语是否存在
说明:遍历带查找的字符串的字符,如果每个节点都存在,并且待查找字符串的最后一个字符对应的Node的isWord属性为 true,则表示该单词存在。
// 查找单词是否存在
public boolean search(String word) {
if (word == null || word.length()==0) {
return false;
}
Node node = root;
char[] ch = word.toCharArray(); // 转换成char
for (char c : ch) {
// 判断节点是否存在
Node child = node.next.get(c);
if (child == null) {
return false;
}
node = child;
}
return node.isWord;
}
第四步:根据词语的前缀查找是否存在
// 根据词语的前缀查找是否存在
public int containsPrefix(String word) {
if (word == null || word.length()==0) {
return 0;
}
Node node = root;
char[] ch = word.toCharArray(); // 转换成char
for (char c : ch) {
// 判断节点是否存在
Node child = node.next.get(c);
if (child == null) {
return 0;
}
node = child;
}
// 返回数量
return node.prefixNum;
}
第五步:删除词语
场景一:如果单词是另一个单词的前缀,只需要把该word的最后一个节点的isWord的改成false。
场景二:如果单词的所有字母的都没有多个分支,删除整个单词。
场景三:如果单词的除了最后一个字母,其他的字母有多个分支。
// 删除词语
public boolean remove(String word) {
if (word == null || word.length()==0) {
return false;
}
Node childNode = null;
int childNodeIndex = -1;
Node node = root;
char[] ch = word.toCharArray(); // 转换成char
for (int i=0; i<ch.length; i++) {
// 判断节点是否存在
Node child = node.next.get(ch[i]);
if (child == null) {
return false;
}
// 当前节点的子节点大于1个,记录是否有多个分支
if (child.next.size() > 1) {
childNodeIndex = i;
childNode = child;
}
node = child;
}
// 场景一: 如果单词是另一个单词的前缀,只需要把该word的最后一个节点的isWord的改成false
if (!node.next.isEmpty() && node.isWord) {
node.isWord = false;
size--;
return true;
}
// 场景二: 如果单词的所有字母的都没有多个分支,删除整个单词
if (childNodeIndex == -1) {
root.next.remove(ch[0]);
size--;
return true;
}
// 场景三: 如果单词的除了最后一个字母,其他的字母有多个分支
if (childNodeIndex != ch.length - 1) {
childNode.next.remove(ch[childNodeIndex + 1]);
size--;
return true;
}
return false;
}
第六步:根据字典树还原词语,相当于字典树遍历
// 根据字典树还原词语,相当于字典树遍历
public void print() {
Node node = root;
// 打印
print(node, "", false);
}
private void print(Node node, String word, boolean isWord) {
// 判断节点是否为空
if (node != null) {
// 只输出有效的完整单词,无效的不需要输出
if (isWord) {
System.out.print(word + "\t"); // 可以把所有单词存储起来,方便“检验单词是否包含功能”使用
}
// 递归所有的单词,并查找是否存在,并循环
for (Map.Entry<Character,TrieTree.Node> entry : node.next.entrySet()) {
Node n = node.next.get(entry.getKey());
//System.out.println(n.key + "(" + n.isWord+ "\t:" + n.prefixNum+")" + n.next);
print(n, word + entry.getKey(), n.isWord);
}
}
}
第七步:main方法测试
public static void main(String[] args) {
TrieTree trie = new TrieTree();
// 构建Trie字典树
String[] words = {"abcd", "A", "Accd", "ab", "B", "ccc", "abe", "abf", "abhh"};
for(String word : words) {
trie.add(word);
}
System.out.println("字典树构建之后所有的单词: ");
trie.print();
String word = "abc";
System.out.println("\n\n单词"+word+"是否存在: " + trie.search(word));
word = "abf";
System.out.println("单词"+word+"是否存在: " + trie.search(word));
word = "ab";
System.out.println("前缀"+word+"的单词、有多少个单词以它为前缀: " + trie.containsPrefix(word));
word = "A";
System.out.println("单词"+word+"是否删除: " + trie.remove(word));
word = "ccc";
System.out.println("单词"+word+"是否删除: " + trie.remove(word));
word = "abdd";
System.out.println("单词"+word+"是否删除: " + trie.remove(word));
word = "abe";
System.out.println("单词"+word+"是否删除: " + trie.remove(word));
System.out.println("\n删除之后,字典树中所有的单词: ");
trie.print();
}
打印效果图:
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!