字典树(Trie) 单词查找树
字典树不言而喻就是类似字典的树形数据结构。假如要查找单词dog,那我们肯定是从字典的d部分查起;单词dog和doge自然也是相去不远,因为他们有公共的前缀dog;字典的编排也是按照字典序的…
参考学习《算法竞赛入门到进阶》 -罗勇军、郭卫斌
字典树的常见应用
- 字符串检索、查询
- 词频统计
- 字符串排序
- 前缀匹配(搜索引擎、Linux指令自动补全)
字典树的实现
1. 正规字典树
定义字典树数据结构,用指针指向下一层子树,但是对空间要求较高,示意图如图:
此为字典树节点属性:
//字典树节点
public static class TrieNode{
public TrieNode[] tn; //下一层子树
public int num; //以当前字符串为前缀的单词数量
public TrieNode(){ //构造方法
tn = new TrieNode[26];
}
}
字典树的根节点,不存数据
//字典树根节点
public static TrieNode root = new TrieNode();
插入指定字符串s到字典树,逐个将s中的字母放到对的位置 上
//插入方法
public static void insert(String s){
TrieNode temp = root; //获取根节点
//遍历s
for(int i = 0;i < s.length();i++){
if(temp.tn[s.charAt(i)-'a'] == null)
temp.tn[s.charAt(i)-'a'] = new TrieNode();
temp = temp.tn[s.charAt(i)-'a'];
temp.num++;
}
}
检索单词s或前缀s出现次数
//检索
//与遍历类似
public static int find(String s){
TrieNode temp = root;
for(int i = 0;i < s.length();i++){
if(temp.tn[s.charAt(i)-'a'] == null)
return 0;
temp = temp.tn[s.charAt(i)-'a'];
}
return temp.num;
}
字典序输出字典树
public static void sort(TrieNode root,String s){
//判断是否到达最后一个字母
if(sortCheck(root)){
System.out.println(s);
return;
}
//通过前序遍历实现排序
for(int i = 0;i < 26;i++){
if(root.tn[i] != null){
String temp = s;
temp += String.valueOf((char)('a'+i));
sort(root.tn[i],temp);
}
}
}
public static boolean sortCheck(TrieNode root){
for(int i = 0;i < 26;i++)
if(root.tn[i] != null)
return false;
return true;
}
完整源代码如下
public class Trie {
public static TrieNode root = new TrieNode();
public static class TrieNode{
public TrieNode[] tn;
public int num;
public TrieNode(){
tn = new TrieNode[26];
}
}
public static void insert(String s){
TrieNode temp = root;
for(int i = 0;i < s.length();i++){
if(temp.tn[s.charAt(i)-'a'] == null)
temp.tn[s.charAt(i)-'a'] = new TrieNode();
temp = temp.tn[s.charAt(i)-'a'];
temp.num++;
}
}
public static int find(String s){
TrieNode temp = root;
for(int i = 0;i < s.length();i++){
if(temp.tn[s.charAt(i)-'a'] == null)
return 0;
temp = temp.tn[s.charAt(i)-'a'];
}
return temp.num;
}
public static boolean sortCheck(TrieNode root){
for(int i = 0;i < 26;i++)
if(root.tn[i] != null)
return false;
return true;
}
public static void sort(TrieNode root,String s){
//判断是否到达最后一个字母
if(sortCheck(root)){
System.out.println(s);
return;
}
//通过前序遍历实现排序
for(int i = 0;i < 26;i++){
if(root.tn[i] != null){
String temp = s;
temp += String.valueOf((char)('a'+i));
sort(root.tn[i],temp);
}
}
}
public static void main(String[] args) {
insert("gerge");
insert("eroni");
insert("cwrf");
insert("oqpwinfw");
insert("wnkca");
insert("axcnasixu");
insert("abf");
insert("abcesf");
sort(root,"");
}
}
2. 数组实现字典树
竞赛中常用,是一种更为紧凑的存储方式。
基本属性
//用数组定义字典树,存储下一个字符的位置(注意是位置)
public static int[][] trie = new int[100000][26];
//以某一字符串为前缀的单词的数量
public static int[] num = new int[100000];
//当前新分配的存储位置
public static int pos=1;
数组实现与第一种实现大致相同,主要是注意理解pos是新分配的存储位置(行数),笔者建议代入几条数据模拟一下,完整源代码如下
public class TrieArray {
public static int[][] trie = new int[100000][26];
public static int[] num = new int[100000];
public static int pos=1;
public static void main(String[] args) {
insert("gerge");
insert("eroni");
insert("cwrf");
insert("oqpwinfw");
insert("wnkca");
insert("axcnasixu");
insert("abf");
insert("abcesf");
sort(0,"");
}
public static boolean sortCheck(int line){
for(int i = 0;i < 26;i++)
if(trie[line][i] != 0)
return false;
return true;
}
public static void sort(int line,String s){
if(sortCheck(line)){
System.out.println(s);
return;
}
for(int i = 0;i < 26;i++){
String temp = s;
if(trie[line][i] != 0){
temp += String.valueOf((char)('a'+i));
sort(trie[line][i],temp);
}
}
}
public static void insert(String s){
int p = 0;
for(int i = 0;i < s.length();i++){
int n = s.charAt(i) - 'a';
if(trie[p][n] == 0)
trie[p][n] = pos++;
p = trie[p][n];
num[p]++;
}
}
public static int find(String s){
int p = 0;
for(int i = 0;i < s.length();i++){
int n = s.charAt(i) - 'a';
if(trie[p][n] == 0)
return 0;
p = trie[p][n];
}
return num[p];
}
}
复杂度
- 时间复杂度:插入和查找单词的复杂度都是O(m),m是待插入/查询字符串的长度
- 空间复杂度:有公共前缀的单词只需要存一次公共前缀,节省了空间