美文网首页
数据结构必知 --- 前缀树

数据结构必知 --- 前缀树

作者: _code_x | 来源:发表于2021-07-15 14:34 被阅读0次

    写在前

    • 什么是字典树?

      1. Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。Trie 一词来自 retrieval,发音为 /tri:/ "tree",也有人读为 /traɪ/ "try"。
      2. 典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较。

      Trie的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

    • 基本性质

      1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。

      2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

      3. 每个节点的所有子节点包含的字符都不相同。

    • 复杂度分析

      1.插入和查询的效率很高,时间复杂度都为O(m),其中 m 是待插入/查询的字符串的长度

      注:关于查询,会有人说 hash 表时间复杂度是O(1)不是更快?但是,哈希搜索的效率通常取决于 hash 函数的好坏,若一个坏的 hash 函数导致很多的冲突,效率并不一定比Trie树高。

      2.最坏的空间复杂度实现方式可以到S^L,其中S为字符集L为最长长度

    • Trie树的应用

      • 字符串检索:事先将已知的一些字符串(字典)的有关信息保存到 Trie 里,查找另外一些未知字符串是否出现过或者出现频率。
      • 字符串最长公共前缀:Trie 利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵 Trie 上时,我们可以快速得到某些字符串的公共前缀。
      • 排序:Trie 树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串,便是按字典序排序的结果。
      • 作为其他数据结构和算法的辅助结构:如后缀树,AC自动机等。

    基本实现

    前缀树创建

    假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,那我们创建trie树就得到。

    可以看出,Trie树的关键字一般都是字符串,而且Trie树把每个关键字保存在一条路径上,而不是一个结点中。另外,两个有公共前缀的关键字,在Trie树中前缀部分的路径相同。

    前缀树的主要操作就是插入和查询,即将一个字符串加入集合中和查询集合中有没有这个字符串。每个字符加两个变量,path 定义节点经过的次数,end 定义最后一个插入的节点

    public class TrieNode {
        private int path;
        private int end;
        private TrieNode[] nexts;
        
        public TrieNode() {
            path = 0;
            end = 0;
            nexts = new TrieNode[26];
        }
    }
    

    前缀树插入

    private TrieNode root;  // 根节点
    
    public TrieNode {
        root = new TrieNode();
    }
    
    /**
        往前缀树中插入字符串(这里只包含26个小写字母)
    */
    private void insert(String word) {
        if (word == null) {
            return;
        }
        int n = word.length();
        char[] chs = word.toCharArray();
        TrieNode node = root;
        int index = 0;
        
        for (int i = 0; i < n; i++) {
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {
                node.nexts[index] = new TrieNode();    // 创建当前字符
            }
            node = node.nexts[index];
            node.path++;
        }
        node.end++;
    }
    

    前缀树查询

    /**
        1、查询前缀树某字符串出现的次数,返回结尾字符出现的次数,即node.end
        2、查询以某字符串为前缀的数量,返回每个字符被划过的次数,即node.path
        如果不存在返回-1,类似插入操作!
    */
    private int searchCount(String str) {
        if (str == null) {
            return -1;
        }
        int n = str.length();
        char[] chs = str.toCharArray();
        TrieNode node = root;
        int index = 0;
        
        for (int i = 0; i < n; i++) {
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {
                return -1;    
            }
            node = node.nexts[index];  
        }
        return node.end;    // 返回以当前关键字结尾的字符串的个数
        //return node.path;  //插字符串时到达几次
    }
    
    /**
        3、查询集合中是否存在某个字符串,同理
    */
    private boolean search(String str) {
        if (str == null) {
            return false;
        }
        int n = str.length();
        char[] chs = str.toCharArray();
        TrieNode node = root;
        int index = 0;
        
        for (int i = 0; i < n; i++) {
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {
                return false;    
            }
            node = node.nexts[index];  
        }
        return true;   
    }
    

    案例分析

    T208 前缀树

    代码实现:

    class Trie {
    
        class TrieNode {
            boolean isEnd;
            TrieNode[] nexts = new TrieNode[26];
        }
    
        TrieNode root;
    
        /** Initialize your data structure here. */
        public Trie() {
            root = new TrieNode(); 
        }
        
        /** Inserts a word into the trie. */
        public void insert(String word) {
            if (word == null) {
                return;
            }
            TrieNode node = root;
            int index = 0;
    
            for (int i = 0; i < word.length(); i++) {
                index = word.charAt(i) - 'a';
                if (node.nexts[index] == null) {
                    node.nexts[index] = new TrieNode();
                }
                node = node.nexts[index];
            }
            node.isEnd = true;
        }
    
        /** Returns if the word is in the trie. */
        public boolean search(String word) {
            if (word == null) {
                return false;
            }
            TrieNode node = root;
            int index = 0;
     
            for (int i = 0; i < word.length(); i++) {
                index = word.charAt(i) - 'a';
                if (node.nexts[index] == null) {
                    return false;
                }
                // 指针移动
                node = node.nexts[index];
            }
            return node.isEnd;
        }
        
        /** Returns if there is any word in the trie that starts with the given prefix. */
        public boolean startsWith(String prefix) {
            if (prefix == null) {
                return false;
            }
            TrieNode node = root;
            int index = 0;
     
            for (int i = 0; i < prefix.length(); i++) {
                index = prefix.charAt(i) - 'a';
                if (node.nexts[index] == null) {
                    return false;
                }
                node = node.nexts[index];
            }
            return true;
        }
    }
    
    /**
     * Your Trie object will be instantiated and called as such:
     * Trie obj = new Trie();
     * obj.insert(word);
     * boolean param_2 = obj.search(word);
     * boolean param_3 = obj.startsWith(prefix);
     */
    

    T421 数组最大异或值

    待补充。。。

    相关文章

      网友评论

          本文标题:数据结构必知 --- 前缀树

          本文链接:https://www.haomeiwen.com/subject/eyqypltx.html