字典树学习总结

1、字典树

字典树别名有Tire树、前缀树(prefix tree)、键树,是一种多叉树结构:
java

上图演示的是a、to、hero三个单词的字典树结构,从上图咱们能够概括出Trie树的基本性质node

  • 根节点不包含字符,出根节点之外的每个子节点都包含一个字符。
  • 从根节点到某一个节点,路径上通过的字符连起来,为该节点所对应的字符串。
  • 每一个节点的全部子节点包含的字符互不相同。

由此咱们得知一个字典树的节点应该包含字符所链接子节点的指针,不过通常还要设置一个标志,用来标志该节点处是否构成一个单词数组

struct trie_node{
     int count;//0:不构成一个单词,>0:构成一个单词
     struct trie_node *children[26]; //如上图,'to'中o对应节点的children数组值全为NULL
}

能够看出,Trie树的关键字通常都是字符串,并且Trie树把每一个关键字都保存在一个路径上,而不是一个节点中。另外,有两个公共前缀的关键字,在Trie树中前缀部分路径相同。因此Trie又称为前缀树函数

字典树的应用:

  • 字符串检索
    检索、查询功能是字典树最原始的功能之一,原理就是从根节点开始一个个字符比较
    一、比较过程当中字典树显示该节点为NULL,则说明该字符串在字典树中不存在。
    二、比较到字符串最后一个节点时,检查当前字典树节点标识位是否标识为一个关键字。ui

  • 词频统计
    这里咱们只需将count修改为一个计数器,对每个关键字进行插入操做时,count++;搜索引擎

  • 字符串排序
    咱们从字典树树结构能够看出,它每个节点数组长度都是26,当字符存在时,数组相应位置不为NULL,因此咱们只需先序遍历输出Trie树中插入的全部关键字便可。编码

  • 前缀匹配
    例如:找出一个字符串集合中全部以ab开头的字符串。咱们只需用全部字符串构造一个Trie树,而后输出以a->b开头的路径上的关键字便可。通常咱们用的搜索引擎也体现了这种思想:指针

字典树例题:

单词的压缩编码code

1、题目:

给定一个单词列表,咱们将这个列表编码成一个索引字符串 S 与一个索引列表 A。blog

例如,若是这个列表是 ["time", "me", "bell"],咱们就能够将其表示为 S = "time#bell#" 和 indexes = [0, 2, 5]。

对于每个索引,咱们能够经过从字符串 S 中索引的位置开始读取字符串,直到 "#" 结束,来恢复咱们以前的单词列表。

那么成功对给定单词列表进行编码的最小字符串长度是多少呢?

示例

输入:words=["time","me","bell"]
输出:10
说明:S="time#bell#",indexes=[0,2,5]

提示

  • 1<=words.length<=2000
  • 1<=word[i].length<=7
  • 每一个单词都是小写字母

思路
这个题目读上去挺拗口的,但意思挺简单的,就是若是一个单词能够表示为另外一个单词的后缀
便可当作一个单词,例如:"time"和"me",me为time的后缀,因此它们能够看出一个单词time。至此咱们能够与字典树联系起来了,但有人可能会说这是后缀类型的,而字典树是判断前缀的,其实咱们只需将单词逆序插入就好了。

好比题目中的 ["time","me","bell"] 的逆序就是 ["emit","em","lleb"] 。能够发现ememit的前缀。可是咱们必须先插入单词长的字符串,不然就会产生问题,这里能够阅读代码理解。因此咱们插入以前须要根据单词的长度由长到短排序。

代码

class Solution {
    public int minimumLengthEncoding(String[] words) {
        int len = 0;
        Trie trie = new Trie();
        // 先对单词列表根据单词长度由长到短排序
        Arrays.sort(words, (s1, s2) -> s2.length() - s1.length());
        // 单词插入trie,返回该单词增长的编码长度
        for (String word: words) {
            len += trie.insert(word);
        }
        return len;
    }
}

// 定义tire
class Trie {
    
    TrieNode root;
    
    public Trie() {
        root = new TrieNode();
    }

    public int insert(String word) {
        TrieNode cur = root;
        boolean isNew = false;
        // 倒着插入单词
        for (int i = word.length() - 1; i >= 0; i--) {
            int c = word.charAt(i) - 'a';
            if (cur.children[c] == null) {
                isNew = true; // 是新单词
                cur.children[c] = new TrieNode();
            }
            cur = cur.children[c];
        }
        // 若是是新单词的话编码长度增长新单词的长度+1,不然不变。
        //若是是短单词先插入的话,会计算短单词的长度,后面长单词也会计算,然而短单词是 
       //长单词的后缀,应舍去
        return isNew? word.length() + 1: 0;
    }
}

class TrieNode {
    char val;
    TrieNode[] children = new TrieNode[26];

    public TrieNode() {}
}

题解参考

总结:

至此咱们已经对字典树有了初步理解,通常遇到须要大量判断一个字符串是否是给点单词列表中的前缀或后缀,能够往字典树方向思考,相比于用 HashMap,节省了大量运行时间和存储空间,HashMap的效率取决于哈希函数的好坏,若一个坏的哈希函数致使了不少冲突,效率不必定比Trie树高。

相关文章
相关标签/搜索