当前位置:   article > 正文

字典树 - 核心思想及其代码实现_字典树如何构建

字典树如何构建

一、简介

Trie 树也称为字典树、单词查找树,最大的特点就是共享字符串的公共前缀来达到节省空间的目的。

例如,字符串 “abc” 和 “abd” 构成的 trie 树如下:

在这里插入图片描述

字典树的根节点不存任何数据,每整个分支代表一个完整的字符串。像 abc 和 abd 有公共前缀 ab,所以我们可以共享节点 ab。

如果再插入 abf,则变成这样:

在这里插入图片描述
如果我再插入 bc,则是这样(bc 和其他三个字符串没有公共前缀)

在这里插入图片描述
那如果再插入 “ab” 这个字符串呢?

每个分支的内部可能也含有完整的字符串,所以我们可以对于那些是某个字符串结尾的节点做一个标记,例如 abc,abd,abf 都包含了字符串 ab,所以我们可以在节点 b 这里做一个标记。如下(我用红色作为标记):

在这里插入图片描述

二、应用

2.1 提前列出搜索信息

字典树最大的特点就是利用了字符串的公共前缀,像我们有时候在百度、谷歌输入某个关键字的时候,它会给我们列举出很多相关的信息。

在这里插入图片描述

2.2 敏感词过滤

例:一段字符串 “abcdefghi" ,以及三个敏感词 “de” ,“bca” ,“bcf” 。

先把三个敏感词建立一颗字典树,如下:

在这里插入图片描述

接着可以采用三个指针来遍历。

首先指针 p1 指向 root ,指针 p2 和 p3 指向字符串第一个字符。

在这里插入图片描述

然后从字符串的 a 开始,检测有没有以 a 作为前缀的敏感词,直接判断 p1 的孩子节点中是否有 a 这个节点就可以了,显然这里没有。

接着把指针 p2 和 p3 向右移动一格。

在这里插入图片描述

然后从字符串 b 开始查找,看看是否有以 b 作为前缀的字符串,p1 的孩子节点中有 b,这时,我们把 p1 指向节点 b ,p2 向右移动一格,不过,p3 不动。

在这里插入图片描述

判断 p1 的孩子节点中是否存在 p2 指向的字符 c ,显然有。我们把 p1 指向节点 c ,p2 向右移动一格,p3 不动。

在这里插入图片描述

判断 p1 的孩子节点中是否存在 p2 指向的字符 d ,这里没有。这意味着,不存在以字符 b 作为前缀的敏感词。这时我们把 p2 和 p3 都移向字符 c ,p1 还是还原到最开始指向 root 。

在这里插入图片描述

和前面的步骤一样,判断有没以 c 作为前缀的字符串,显然这里没有,所以把 p2 和 p3 移到字符 d 。

在这里插入图片描述

然后从字符串 d 开始查找,看看是否有以 d 作为前缀的字符串,p1 的孩子节点中有 d ,这时,我们把 p1 指向节点 b ,p2 向右移动一格,不过,p3 和刚才一样不动。

在这里插入图片描述
判断 p1 的孩子节点中是否存在 p2 指向的字符 e ,显然有。我们把 p1 指向节点 e ,并且,这里 e 是最后一个节点了,查找结束,所以存在敏感词 de ,即 p3 和 p2 这个区间指向的就是敏感词了,把 p2 和 p3 指向的区间那些字符替换成 * 。并且把 p2 和 p3 移向字符 f 。如下:

在这里插入图片描述

接着还是重复同样的步骤,直到 p3 指向最后一个字符。

复杂度分析

如果敏感词的长度为 m,则每个敏感词的查找时间复杂度是 O(m),字符串的长度为 n,我们需要遍历 n 遍,所以敏感词查找这个过程的时间复杂度是 O(n * m)。如果有 t 个敏感词的话,构建字典树的时间复杂度是 O(t * m)。

这里我说明一下,在实际的应用中,构建字典树的时间复杂度我觉得可以忽略,因为字典树我们可以在一开始就构建了,以后可以无数次重复利用。

字典树可以采用 HashMap 来实现,因为一个节点的字节点个数未知,采用 HashMap 可以动态拓展,而且可以在 O(1) 复杂度内判断某个子节点是否存在。

三、敏感词过滤代码实现

3.1 字典树结点
public class TrieNode {

    // 是否敏感词的结尾
    private boolean isEnd = false;

    // 下一层节点
    Map<Character, TrieNode> subNodes = new HashMap<>();

    /**
     * 添加下一个敏感词节点
     */
    public void addSubNode(Character c, TrieNode node) {
        subNodes.put(c, node);
    }

    /**
     * 获取下一个敏感词节点
     */
    public TrieNode getSubNode(Character c) {
        return subNodes.get(c);
    }

    /**
     * 设置敏感词标识
     */
    public void setEnd(boolean end) {
        this.isEnd = end;
    }

    /**
     * 判断是否为敏感词的结尾
     */
    public boolean getIsEnd() {
        return this.isEnd;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
3.2 构建字典树
public class TireTree {

    // 敏感词替换字符
    private static final String DEFAULT_REPLACEMENT = "*";

    // 根节点
    private final TrieNode rootNode;

    public TireTree() {
        rootNode = new TrieNode();
    }

    /**
     * 识别特殊符号
     */
    private boolean isSymbol(Character c) {
        int ic = (int) c;
        // 0x2E80-0x9FFF 东亚文字范围
        return !CharUtils.isAsciiAlphanumeric(c) && (ic < 0x2E80 || ic > 0x9FFF);
    }

    /**
     * 将敏感词添加到字典树中
     */
    public void addWord(String text) {
        if (text == null || text.trim().equals(""))
            return;
        TrieNode curNode = rootNode;
        int length = text.length();
        // 遍历每个字
        for (int i = 0; i < length; ++i) {
            Character c = text.charAt(i);
            // 过滤特殊字符
            if (isSymbol(c))
                continue;
            TrieNode nextNode = curNode.getSubNode(c);
            // 第一次添加的节点
            if (nextNode == null) {
                nextNode = new TrieNode();
                curNode.addSubNode(c, nextNode);
            }
            curNode = nextNode;
            // 设置敏感词标识
            if (i == length - 1)
                curNode.setEnd(true);
        }
    }

    /**
     * 过滤敏感词
     */
    public String filter(String text) {
        if (text == null || text.trim().equals(""))
            return text;
        StringBuilder result = new StringBuilder();
        TrieNode curNode = rootNode;
        int begin = 0; // 回滚位置
        int position = 0; // 当前位置
        while (position < text.length()) {
            Character c = text.charAt(position);
            // 过滤空格等
            if (isSymbol(c)) {
                if (curNode == rootNode) {
                    result.append(c);
                    ++begin;
                }
                ++position;
                continue;
            }
            curNode = curNode.getSubNode(c);
            // 当前位置的匹配结束
            if (curNode == null) {
                // 以begin开始的字符串不存在敏感词
                result.append(text.charAt(begin));
                // 跳到下一个字符开始测试
                position = begin + 1;
                begin = position;
                // 回到树初始节点,重新匹配
                curNode = rootNode;

            } else if (curNode.getIsEnd()) {
                // 发现敏感词,从begin到position的位置用replacement替换掉
                result.append(DEFAULT_REPLACEMENT);
                position = position + 1;
                begin = position;
                // 回到树初始节点,重新匹配
                curNode = rootNode;
            } else {
                ++position;
            }
        }
        result.append(text.substring(begin));
        return result.toString();
    }
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
3.3 测试
class Test {

    public static void main(String[] args) {
        TireTree tree = new TireTree();
        // 添加敏感词
        tree.addWord("de");
        tree.addWord("bca");
        tree.addWord("bcf");
        // 过滤敏感词
        String res = tree.filter("abcdefghi");
        System.out.println(res); // abc*fghi
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

一个值得尝试的 AI 赚钱小项目

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号