(六)树结构---字典树

1.字典树基础

1.1.字典树

  • 字典树又称前缀树,对于一个字符串在加入字典树结构时,会合并相同的字符,字典树是一种多叉树
  • 对于一个字符串的结尾字符,专门做一个标识,来表示存在一个字符串以此单词为结尾,从而避免子串被覆盖
  • 根结点并不存储任何字符,仅代表着一个根,实际上字典树的效率取决于字符串的长度,也就是树的深度,而与字符串的数量没有关系。实际上对字符串来说,串长度不会太长,这也决定了这种数据结构的效率比较高
  • 字典树在大量数据的情况下任然可以保持很高的效,这一性能是大量空间消耗换取的,但对于现在计算机而言,字典树带来的益处远比损耗要大,因此很有实际意义。
  • 笔者实现的仅为最基础的字典树,实际上字典树对于减小空间消耗也有很多解决办法,这些变种在减少空间消耗的情况下,也会增加维护成本,代码复杂度提高和效率上会稍稍降低。

1.2.逻辑结构

  • 直接举例说明,我们对字符串"adb","adbef","db","def","der","cc"构造字典树。


    构造字典树.png

1.3.物理结构

  • 在构造字典树时,比较常规的实现是使用Map结构
  • 若字典树存储的是以英文字母为基础的单词字符串,则也可以固定长度,采用数组形式,每层节点数目为26个空间;但是实际上我们并不一定每层都会用到如此多的空间,会造成很大的浪费。
  • 若对于存储为汉字或拥有其他特殊字符的字符出啊,采用Map结构就可以使用到便开辟空间,使空间使用率提高
    上述字典树的物理结构:


    字典树物理结构.png

2.字典树实现

2.1.构造函数

public class Trie {

    private class Node {
        //描述当前结点是否为一个单词
        public boolean isWord;
        public TreeMap<Character, Node> map;

        public Node(boolean isWord) {
            this.isWord = isWord;
            this.map = new TreeMap<>();
        }

        public Node() { this(false); }
    }

    private Node root;
    private int size;

    public Trie() {
        root = new Node();
        size = 0;
    }

    /**
     * 获取trie中存储的单词数量
     * @return :
     */
    public int getSize() {  return size; }

2.2.新添一个字符串

 /**
     * 向trie中添加一个新的单词
     * @param word :
     */
    public void add(String word) {
        Node cur = root;
        for (int i = 0; i < word.length(); i++) {
            Character c = word.charAt(i);
            //若该字符不存在于当前结点保存的映射中,则新填一个映射保存该字符
            if (cur.map.get(c) == null) {
                cur.map.put(c, new Node());
            }
            //当前结点移动到该字符的结点处
            cur = cur.map.get(c);
        }
        //当移动到单词最后一个字符,若字符为false,说明没有该字符
        if (!cur.isWord) {
            cur.isWord = true;
            size++;
        }
    }

1.遍历以此保存每一个字符,若存在不进行操作,不存在添加该单词
2.标识最后一个字符为单词结尾

2.2.判断该单词是否存在

 /**
     * 查询trie中是否有word这个单词
     * @return :
     */
    public boolean contains(String word){
        Node cur = root;
        for (int i = 0; i < word.length(); i++) {
            Character c = word.charAt(i);
            //若有一个字符不匹配,则单词不匹配
            if (cur.map.get(c) == null) {
                return false;
            }
            cur = cur.map.get(c);
        }
        //当遍历到最后一个单词后,判断是否存在该单词
        return cur.isWord;
    }

1.遍历确定是否每一个字符都存在
2.判断最后一个字符是否是一个字符串的结尾

2.3.判断是否存在以prefix为前缀的单词

 /**
     * 查询trie中是否有以prefix为前缀的单词
     * @param prefix :单词前缀
     * @return :
     */
    public boolean isPrefix(String prefix){
        Node cur = root;
        for (int i = 0; i < prefix.length(); i++) {
            Character c = prefix.charAt(i);
            if(cur.map.get(c) == null){
                return false;
            }
            cur = cur.map.get(c);
        }
        //当遍历完,前缀字符都拥有,所以有以此为前缀的单词
        return true;
    }

3.字典树测试

PS:游戏中的敏感词过滤是如何实现的

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容