由字典树想到的
由字典树想到的
- 字典树
- 双数组树
- AC自动机双数组树
基于数组实现的字典树
public class TrieST<Value> {
private static int R = 256;
private Node root;
public static class Node{
private Object val;
private Node[] next = new Node[R];
}
public Value get(String key) {
Node x = get(root, key, 0);
if (x == null) {
return null;
}
return (Value) x.val;
}
private Node get(Node x, String key, int d) {
if (x == null) {
return null;
}
if (d == key.length()) {
return x;
}
char c = key.charAt(d);
return get(x.next[c], key, d + 1);
}
public void put(String key, Value val) {
root = put(root, key, val, 0);
}
private Node put(Node x, String key, Value val, int d) {
if (x == null) {
x = new Node();
}
if (d == key.length()) {
x.val = val;
return x;
}
char c = key.charAt(d);
x.next[c] = put(x.next[c], key, val, d + 1);
return x;
}
public static void main(String[] args) {
TrieST<String> stringTrieST = new TrieST<>();
stringTrieST.put("a", "1");
String val = stringTrieST.get("a");
System.out.println(val);
}
}
- 基于数组实现的字典树,每个节点都有一个长度为R的数组。空间复杂度高。
- 查找成功的时间复杂度为O(logN)
基于HashMap实现的字典树
import java.util.HashMap;
import java.util.Map;
public class MapTrieST<Value> {
private Node root;
private static class Node{
private Object val;
private Map<Character, Node> next = new HashMap<>();
}
public Value get(String key) {
Node x = get(root, key, 0);
// if (x == null) {
// return null;
// }
return (Value) x.val;
}
private Node get(Node x, String key, int d) {
if (x == null) {
return null;
}
if (d == key.length()) {
return x;
}
Character ch = key.charAt(d);
return get(x.next.get(ch), key, d + 1);
}
public void add(String key, Value val) {
root = add(root, key, val, 0);
}
private Node add(Node x, String key, Value val, int d) {
if (x == null) {
x = new Node();
}
if (d == key.length()) {
x.val=val;
return x;
}
Character ch = key.charAt(d);
x.next.put(ch, add(x.next.get(ch), key, val, d + 1));
return x;
}
public static void main(String[] args) {
MapTrieST<String> mapTrieST = new MapTrieST<>();
mapTrieST.add("i", "1");
mapTrieST.add("love", "2");
mapTrieST.add("u", "3");
System.out.println(mapTrieST.get("i"));
System.out.println(mapTrieST.get("l"));
System.out.println(mapTrieST.get("u"));
System.out.println(mapTrieST.get("x"));
}
}
- 使用HashMap来保存所有的孩子节点,当孩子节点很多时,Map不可避免地存在Hash冲突的问题
- 空间利用率要比基于数组实现的字典树高
- 基于数组实现的字典树,访问下一个节点时是通过数组下标进行定位;基于HashMap实现的字典树访问下一个节点是通过Hash查找定位,时间复杂度可以都认为是O(1)
双数组字典树
针对上面描述的基于数组实现的字典树的进行优化,大大提高了空间利用率。为了理解双数组字典树,可以先学习一下HanLP里面的BinTire,它对词典里面的词的第一个字符进行散列(首字散列),后续的字符采用'二分查找树'这样的一种数据结构(其余二分)。而二分查找的时间复杂度是O(logN),N为所有子节点的个数。
而双数组字典树,则用了2个数组:base[] 和 check[] 进行状态的转移,即:当状态b 接受 字符c 转移到 状态p 时,
p=base[b]+c
check[p]=base[b]
若不满足上面公式,则认为状态转换失败。状态转移通过“加法操作”和"比较操作"实现,是一个常数时间。因而能够更快地找到文本中命中词典里面的词。
引入AC自动机的双数组字典树
在双数组字典树的基础上引入AC自动机,从而具有多模式匹配的功能。
双数组字典树和ACDAT(引入了AC自动机的双数组字典树)可参考hancks的开源项目。
一些问题:
-
字典树与红黑树的对比
key的有序性?是否有效支持更新操作?
-
特殊字符的匹配命中
基于数组的实现字典树是通过数组下标寻找下一个节点的,如果有些十分特殊的字符超出了数组的表示范围,那就无法用来字典树匹配特殊字符了。
有一种不太优雅的实现思路是:基于HashMap来实现字典树,但是HashMap的Key并不是词典中的词,而是每一个字符的编码。这样,不管是什么样的特殊字符,它都有一个编码,用这个编码作为Key,就能构造含有特殊的字典树了。
-
中文分词中为什么会用到双数组字典树?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端