又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 ----百度
具体图文表述请看别的技术博客,这里我分别用数组和map来表示树的下一个节点,具体代码实现和细节如下:
数组实现:需要一开始就初始化,比如有26个字母,就将下一个节点集合变成大小为26的节点数字
package cn.nupt;
/**
* @Description: 前缀树(单词查找树),数组表示
*
* @author PizAn
* @Email [email protected]
* @date 2019年3月6日 下午3:32:33
*
*/
public class TrieTree {
static class TrieNode { //节点类,就跟链表、二叉树一样
private int path; //经过这个节点的路径 的数目,用来计算前缀数
private int end; //以这个节点为尾的字符串的数目
private TrieNode[] next; //指向下一个节点的指针(就跟链表一样)
public TrieNode() {
path = 0;
end = 0;
next = new TrieNode[26]; //因为是a~z,所以这里初始化为26
}
}
// 默认初始化一个头节点
public static class Trie { //这里再来一个内部类来放方法
private TrieNode root;
public Trie() {
root = new TrieNode();
}
/**
* @Description: 压入字符串
* @param word
*
*/
public void insert(String word) {
if (word == null) {
return;
}
TrieNode node = root;
int index = 0;
char[] cha = word.toCharArray();
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
// 每一个点都可以对应26个方向,而这个步骤是找到这个方向(index)看这个方向有没有东西,没有的话就创建一个
// 有的话就直接跳到下一个
if (node.next[index] == null) {
node.next[index] = new TrieNode();
}
node = node.next[index];
node.path++; // 将路径记录+1
}
node.end++; // 在最后一个节点记录一下
}
/**
* @Description: 查找字符串,并返回字符串个数
* @param word
* @return
*
*/
public int search(String word) {
if (word == null) {
return 0;
}
TrieNode node = root;
int index = 0;
char[] cha = word.toCharArray();
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
if (node.next[index] == null) {//如果找到下一个节点为空,直接返回,说明没有这个单词
return 0;
}
node = node.next[index];
}
return node.end;
}
/**
* @Description: 删除字符串(就是把沿途的path--,最后一个end--,如果路径中的path减到0了,说明以后没有东西了,直接将这个节点指空)
* @param word
*
*/
public void delete(String word) {
// 如果没有输入字符串,返回
if (word == null) {
return;
}
// 如果没有搜索到字符串,直接返回
if (search(word) == 0) {
return;
} else {
char[] cha = word.toCharArray(); // 每次一上来先把字符串变成字符数组
TrieNode node = root;// 每次都是从这个头节点开始操作
int index = 0; // 每次都要初始化一个索引,来记录这个字符是第几个
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
if (node.next[index].path == 1) { // 如果路径等于1,表示下面是一条独径(前面已经search过了,肯定有这个字符串)
node.next[index] = null; // 直接指空
return;
} else {
node.next[index].path--;// 如果不为空,路径减1,往下走
node = node.next[index];
}
}
node.end--;
}
}
/**
* @Description: 计算前缀的个数(找到前缀的最后一个如果是null返回0,否则返回path)
* @param pre
* @return
*
*/
public int prefixNumber(String pre) {
//其实查前缀和查单词代码是一样的,都是将单词/前缀拆开,然后一个一个找,中间连不下去了说明没有这个单词/前缀,直接返回0,如果能查到最后,分别返回path/end
if (pre == null) {
return 0;
}
//固定三个步骤
char[] cha = pre.toCharArray();
int index = 0;
TrieNode node = root;
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
if (node.next[index] == null) {//如果找到下一个节点为空,直接返回,说明没有这个前缀
return 0;
}
node = node.next[index];
}
return node.path;
}
}
public static void main(String[] args) {
Trie trie = new Trie();
System.out.println(trie.search("zuo"));// 0
trie.insert("zuo");
System.out.println(trie.search("zuo"));// 1
trie.delete("zuo");
System.out.println(trie.search("zuo"));// 0
trie.insert("zuo");
trie.insert("zuo");
trie.delete("zuo");
System.out.println(trie.search("zuo"));// 1
trie.delete("zuo");
System.out.println(trie.search("zuo"));// 0
trie.insert("zuoa");
trie.insert("zuoac");
trie.insert("zuoab");
trie.insert("zuoad");
trie.delete("zuoa");
System.out.println(trie.search("zuoa"));// 0
System.out.println(trie.prefixNumber("zuo"));// 3
}
}
map实现:和数组实现一样,只不过不是预先定义下一个节点的数目,而是用map存起来
package cn.nupt;
import java.util.HashMap;
/**
* @Description: 前缀树(单词查找树),map表示
*
* @author PizAn
* @Email [email protected]
* @date 2019年3月6日 下午3:32:33
*
*/
public class TrieTree2 {
static class TrieNode {
private int path;
private int end;
//private TrieNode[] next;
private HashMap<Integer, TrieNode> next;
public TrieNode() {
path = 0;
end = 0;
//next = new TrieNode[26];
next = new HashMap<Integer, TrieNode>();
}
}
// 默认初始化一个头节点
public static class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
/**
* @Description: 压入字符串
* @param word
*
*/
public void insert(String word) {
if (word == null) {
return;
}
TrieNode node = root;
int index = 0;
char[] cha = word.toCharArray();
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
// 每一个点都可以对应26个方向,而这个步骤是找到这个方向(index)看这个方向有没有东西,没有的话就创建一个
// 有的话就直接跳到下一个
/*if (node.next[index] == null) {
node.next[index] = new TrieNode();
}*/
if(node.next.get(index) == null){
node.next.put(index, new TrieNode());
}
node = node.next.get(index);
node.path++; // 将路径记录+1
}
node.end++; // 在最后一个节点记录一下
}
/**
* @Description: 查找字符串,并返回字符串个数
* @param word
* @return
*
*/
public int search(String word) {
if (word == null) {
return 0;
}
TrieNode node = root;
int index = 0;
char[] cha = word.toCharArray();
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
/*if (node.next[index] == null) {
return 0;
}
node = node.next[index];*/
if(node.next.get(index) == null){
return 0;
}
node = node.next.get(index);
}
return node.end;
}
/**
* @Description: 删除字符串(就是把沿途的path--,最后一个end--,如果路径中的path减到0了,说明以后没有东西了,直接将这个节点指空)
* @param word
*
*/
public void delete(String word) {
// 如果没有输入字符串,返回
if (word == null) {
return;
}
// 如果没有搜索到字符串,直接返回
if (search(word) == 0) {
return;
} else {
char[] cha = word.toCharArray(); // 每次一上来先把字符串变成字符数组
TrieNode node = root;// 每次都是从这个头节点开始操作
int index = 0; // 每次都要初始化一个索引,来记录这个字符是第几个
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
/* if (node.next[index].path == 1) { // 如果路径等于1,表示下面是一条独径(前面已经search过了,肯定有这个字符串)
node.next[index] = null; // 直接指空
return;
} else {
node.next[index].path--;// 如果不为空,路径减1,往下走
node = node.next[index];
}*/
if(node.next.get(index).path == 1){
node.next.put(index, null);
return;
}else {
node.next.get(index).path--;
node = node.next.get(index);
}
}
node.end--;
}
}
/**
* @Description: 计算前缀的个数(找到前缀的最后一个如果是null返回0,否则返回path)
* @param pre
* @return
*
*/
public int prefixNumber(String pre) {
if (pre == null) {
return 0;
}
//固定三个步骤
char[] cha = pre.toCharArray();
int index = 0;
TrieNode node = root;
for (int i = 0; i < cha.length; i++) {
index = cha[i] - 'a';
/* if (node.next[index] == null) {
return 0;
}
node = node.next[index];*/
if(node.next.get(index) == null){
return 0;
}
node = node.next.get(index);
}
return node.path;
}
}
public static void main(String[] args) {
Trie trie = new Trie();
System.out.println(trie.search("zuo"));// 0
trie.insert("zuo");
System.out.println(trie.search("zuo"));// 1
trie.delete("zuo");
System.out.println(trie.search("zuo"));// 0
trie.insert("zuo");
trie.insert("zuo");
trie.delete("zuo");
System.out.println(trie.search("zuo"));// 1
trie.delete("zuo");
System.out.println(trie.search("zuo"));// 0
trie.insert("zuoa");
trie.insert("zuoac");
trie.insert("zuoab");
trie.insert("zuoad");
trie.delete("zuoa");
System.out.println(trie.search("zuoa"));// 0
System.out.println(trie.prefixNumber("zuo"));// 3
}
}