Trie树
1.概述
又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较
,查询效率比哈希树高。
图示
每个节点代表单词每一个单词的字母,打上标记的节点(红色)表示的是这个节点和他上面的所有节点构成的字母是一个单词,(从上到下)
,没打上标记的表示从上到下
不构成一个单词。每个节点都可能有26个儿子节点,
2.链表模拟
2.1定义节点
我们先定义每一个节点的信息,根据上面的讲述,可以知道每一个节点内部都可能有26个儿子节点,这个可以定义一个数组变量
,并且有些节点还必须被标志为一个单词的结尾,这个可以定义一个布尔型的变量,记录这个节点是否是某些单词的结尾即可
注意: trie跟链表的特殊之处就是不需要定义data域,直接使用了内部的节点数组的索引,将每一个字符映射成0-25的索引
class TrieNode{
//26个儿子节点
TrieNode[] child;
//标志位
boolean isEnd;
public TrieNode(){
child = new TrieNode[26];
isEnd = false;
}
}
2.2增查
trie树的操作一般都是增和查,基本不进行删除和修改
class Trie {
//根节点
private TrieNode root;
public Trie() {
root = new TrieNode();
}
//插入操作
public void insert(String word) {
TrieNode p = root;
for(int i = 0; i < word.length(); i ++){
char c = word.charAt(i);
//某一个字母作为索引的节点时null,就创建出来
if(p.child[c - 'a'] == null){
p.child[c - 'a'] = new TrieNode();
}
//就像链表一样,p节点每次都向后移动
p = p.child[c - 'a'];
}
//单词的末尾节点的标志位置为true
p.isEnd = true;
}
//查询某个单词是否在trie树中
public boolean search(String word) {
TrieNode p = root;
for(int i = 0; i < word.length(); i++){
char c = word.charAt(i);
//判断这个节点是否存在,存在继续往后走
if(p.child[c - 'a'] != null) p = p.child[c - 'a'];
//不存在 直接返回false
else return false;
}
//注意:最后不是直接返回true,返回的是当前节点的标志位,可能即使某一个单词的所有字母都在trie树中,但是最后的节点不是某一个单词的结尾,那么表示这个单词也是不存在的
return p.isEnd;
}
//查询是否有特定前缀的单词 思路跟查询都一样,不过最后直接返回true即可,不考虑标志位
public boolean startsWith(String word) {
TrieNode p = root;
for(int i = 0; i < word.length(); i++){
char c = word.charAt(i);
if(p.child[c - 'a'] != null) p = p.child[c - 'a'];
else return false;
}
//直接返回true
return true;
}
}
3.数组模拟
使用数组模拟的话没有像链表模拟好理解,使用数组模拟首先得知道数据范围,不然的话就使用链表模拟即可。
import java.util.Scanner;
public class Main{
static int N = 100010;
private static int[][] root = new int[N][26];
private static int[] cnt = new int[N];
private static int idx = 0;
public static void main(String[] args){
Scanner jin = new Scanner(System.in);
int n = jin.nextInt();
while(n -- > 0){
String op = jin.next();
String str = jin.next();
if("I".equals(op)){
insert(str);
}else {
System.out.println(search(str));
}
}
}
public static void insert(String word){
int p = 0;
for(int i = 0 ; i < word.length(); i++){
int c = word.charAt(i) - 'a';
//这个位置是0 表示这个位置没有字符,为其赋一个值,每次都++idx,所以二维数组中的每一个坐标的值都不一样,即每一个坐标中存储的就是下一个字符的行数,依次进行相连
if(root[p][c] == 0) root[p][c] = ++idx;
//将这个位置上的值赋给p
p = root[p][c];
}
//因为每一个二维数组的坐标的内容都不一样,所以可以作为唯一表示,记录以这个字母作为结尾的单词个数
cnt[p] ++;
}
public static int search(String word){
int p = 0;
for(int i = 0; i < word.length(); i++){
int c = word.charAt(i) - 'a';
if(root[p][c]==0)return 0;
//直接去下一个字符的下一行的对应位置去找 坐标存储的就是下一个字符的行数,每一 个坐标的内容都不一样
else p = root[p][c];
}
return cnt[p];
}
}