前缀树

前缀树别名单词查找树、字典树,结构图为:
image.png
结构特性为:
根节点不存储值,除根节点外每一个节点都只包含一个字符(也可以说是在字符存在路径上)。
对应某一个节点,其子节点的值各不相同
从根节点到某一个节点,路径上经过的字符连接起来,为该叶子节点对应的字符串

应用场景:
前缀匹配
字符串检索
词频统计
优缺点:
查找效率高,耗费内存,典型的以空间换时间。

前缀树:

  • 名称:Trie 字典树 查找树
  • 特点:查找效率高 消耗内存大
  • 应用:字符串检索 词频统计 字符串排序等

敏感词过滤器:

  • 定义前缀树
  • 根据敏感词 初始化前缀树
  • 编写过滤敏感词的方法

    过滤敏感词的检索原理


    利用词的公共前缀缩小查词范围,提高检索效率
    image.png
    image.png
    image.png
    定义三个指针 :指针1 指向敏感词前缀树
    指针2 指向疑似敏感词的开始位置
    指针3 指向疑似敏感词的当前遍历部分
    2,3指针指向字符x,1指针在树找,树中没有x,则x字符不是敏感词,将x记录到StringBuilder
    2,3指针向后移,判断w也不是敏感词,将其记录到StringBuilder中
    移动到a时,疑似敏感词,2指针不动,1,3指针后移进行判断,c与f不同,故abf不是敏感词,指针3退回到指针2处,指针1退回到根节点处
    2,3指针继续向后移一位进行判断,按照上述步骤可以判断bf为敏感词,将其替换为*
    2,3指针移动到a继续判断,直到结束

过滤敏感词的实现

1.定义一个前缀树的结构
  1. private class TireNode{
  2. // 结尾的标记,判断是不是到达了前缀树的叶子节点,如果到达了,代表这是一个敏感词
  3. private boolean isKeyWord = false;
  4. // 存放节点和其子节点 为什么用map
  5. private Map<Character, TireNode> subNodes = new HashMap<>();
  6. public boolean isKeyWord() {
  7. return isKeyWord;
  8. }
  9. public void setKeyWord(boolean keyWord) {
  10. isKeyWord = keyWord;
  11. }
  12. // 为某个节点添加子节点,构造敏感词的前缀树的时候使用
  13. public void setSubNodes(Character c, TireNode tireNode) {
  14. subNodes.put(c, tireNode);
  15. }
  16. // 获取子节点
  17. public TireNode getSubNode(Character c) {
  18. return subNodes.get(c);
  19. }
  20. }

2.根据设定的敏感词构造敏感词前缀树
  1. // 声明一个不存值的根节点
  2. private TireNode rootNode = new TireNode();
  3. // 构造敏感词前缀树
  4. private void addKey(String keyWord) {
  5. TireNode tempNode = rootNode;
  6. for (int i = 0; i < keyWord.length(); i++) {
  7. char c = keyWord.charAt(i);
  8. TireNode subNode = tempNode.getSubNode(c);
  9. // 判断当前字符有没有子节点
  10. if (subNode == null) { // 没有子节点就初始化一个
  11. subNode = new TireNode();
  12. subNode.setSubNodes(c, subNode);
  13. }
  14. // tempNode 移动到 subNode 上,构建下一个字符
  15. tempNode = subNode;
  16. // 设置结束标志
  17. if (i == keyWord.length() - 1) {
  18. tempNode.setKeyWordEnd(true);
  19. }
  20. }
  21. }

什么意思呢?也就是说如果定义的敏感词是abc、bda、abd这三个,那么构造出来的敏感词前缀树的结构为:
image.png

3. 根据输入内容,过滤其中的敏感词
  1. // 过滤
  2. public String filter(String text) {
  3. if (text == null || text.length() == 0) {
  4. return null;
  5. }
  6. int length = text.length();
  7. // 指针1 指向敏感词前缀树
  8. TireNode tempNode = rootNode;
  9. // 指针2 指向疑似敏感词的开始位置
  10. int begin = 0;
  11. // 指针3 指向疑似敏感词的当前遍历部分
  12. int cur = 0;
  13. // 存放结果
  14. StringBuilder sb = new StringBuilder();
  15. while (cur < length) {
  16. char c = text.charAt(cur);
  17. // 检查前缀树的节点
  18. tempNode = tempNode.getSubNode(c);
  19. // 以begin位置的元素开头的字符串不是敏感词
  20. if (tempNode == null) {
  21. sb.append(c);
  22. tempNode = rootNode; // tempNode 重新指向根节点
  23. cur = ++begin; // begin 向前挪一个,cur 也回到begin 位置
  24. } else { // begin ~ cur 疑似是敏感词
  25. if (tempNode.isKeyWordEnd) { // tempNode 指向结尾 就是敏感词
  26. sb.append("***");
  27. tempNode = rootNode;
  28. begin = ++cur;
  29. } else {
  30. // 没到结尾,检查下一个字符
  31. cur++;
  32. }
  33. }
  34. }
  35. sb.append(text.substring(begin));
  36. return sb.toString();
  37. }

4. 测试
  1. public static void main(String[] args) {
  2. MySensitiveFilter sensitiveFilter = new MySensitiveFilter();
  3. String text = "你好啊,赛利亚";
  4. sensitiveFilter.addKeyWord("好啊");
  5. String result = sensitiveFilter.filter(text);
  6. System.out.println(result);
  7. }

image.png