一. 字典树能干啥
1.加快 在超长文本字符串 内 匹配大量关键词 的效率
2.快速统计 关键词 在字符串内 出现的频率或位置(进而能够找到字符串内出现频率最高的词)
比如有以下一段文本
报道称,美国海军正面临严重的战机荒。一名美国海军官员告诉众议院,通过调整航母舰载机联队的战机编制组合,到2025年缓解其攻击战斗机兵力短缺的问题。具体而言,美国海军原定每个舰载机联队配属两个F-35C中队,每个中队10架,调整后将只配属一个中队,数量增至14架。此外,美海军还将接收美空军和空中国民警卫队的部分F-16战斗机用于训练,解放部分舰载战斗机上舰部署,同时激活库存的28架F/A-18E/F。现役F/A-18E/F战斗机将进入“服役寿命改进”计划,服役寿命将由6000飞行小时延长至10000飞行小时。
现在想统计 美国
,美海军
,美国海军
,战斗机
这四个关键词在上文中出现的频率。
因本人太菜,所以第一反应就是 遍历关键词,拿每个关键词去文中匹配,每对应上一次,频率加一。
但是转念一想,文中出现某个关键词的地方可能很少,这样就白白扫描了很多无用的部分,而且有多少个关键词就得扫描文本多少次,花费时间 = 关键词数 × 文本长度,效率有点低。
于是马上又有了第二反应,只扫描一次文本,用文本的每个字符,匹配所有关键词的首字符,如果对应上了哪个关键词,就把该关键词频率加一。
但是转念二想,这和第一反应差不多啊,花费时间 = 文本长度 × 关键词数。
难道二者真的没有区别吗?观察一会儿后,发现以文本为基础匹配关键词这种方法,如果有多个关键词的前缀是一样的,那我可不可以在匹配 美国
之后,顺道也把 美国海军
匹配了呢,相反,如果 美国
匹配失败,美国海军
必然也匹配失败,这样就能够避免匹配其他以 美国
开头的关键词,减少了扫描次数。
我们可以整合 具有相同前缀的关键词 按 由短到长 的方式排列
二. 字典树长啥样
字典树特点:
- 根节点啥也不存
- 每个节点只保存单个字符(复用关键词的相同前缀字符)
- 一条路径,可能包含多个关键词(根节点 → 绿色节点 = 关键词)
- 每个节点有 0 ~ N 个子节点(具有同一前缀字符的关键词越多,则该前缀节点的子节点越多)
三. 匹配过程
假设文本字符串 String document
,字典树节点类型 TireNode
;
用俩变量 int start = 0, end = 0
记录文本字符串的起始、结束索引;
一个变量 TireNode cur = root
记录待匹配的字符节点;
while ( start < document.length() )
cur 问它的子节点们:有没有叫 document[end] 的?
有:cur 指向该节点,end++
如果 cur 是绿色节点,document[start, end] 就对应上了一个关键词
无:证明 [start, end] 区间没有能够匹配的关键词啦,end = ++start,cur = root,下次准备从头匹配
四. 代码实现
1. 确定字典树节点组成
普通二叉树只有俩叉,所以可以用俩变量表示某一节点的 左、右子节点。
对于字典树的任意节点而言,它有多少子节点不确定,所以需要用集合表示,而且需要根据子节点保存的字符找到其引用,因此选用HashMap更适合。
节点属性除了保存自身的数据(字符),保存子节点们(HashMap),还应该有个关键词尾字符结束标记——是否为绿色节点。
2. 代码实现节点class
class TireNode {
Charater value;
boolean tail = false;
HashMap<Charater, TireNode> son = new HashMap<>();
public TireNode(Character c){
value = c;
}
}
3. 根据所给关键词 建立字典树
美国
, 美海军
, 美国海军
, 战斗机
因为 root 啥也不存,所以是预先初始化好的 TireNode root = new TireNode(null)
以插入关键词 美国
为例:
<1> TireNode cur = root
<2> if ( ! cur.son.containsKey('美') ) {
cur.son.put('美', new TireNode('美'))
}
cur = cur.son.get('美')
<3> 插入'国', 步骤同<2>
<4> 最后设置 TireNode('国').tail = true
用代码实现如下