数据结构之字典树的分析与实现

字典树

字典树的结构

Trie树,即字典树,又称单词查找树前缀树键树,是一种树形结构,其典型的应用是用于统计和排序大量的字符串。

字典树的节点结构具有以下两个字段:

  • 最多R个指向子节点的链接Trie[] next,其中每个链接对应字母表数据集中的一个字母;我们可以假设都为小写字符,可以取R=26
  • 一个布尔类型isEnd,用来标志当前节点是对应键的结尾(字符串的结尾)还是只是键前缀(字符串的一个前缀)。

字典树具有如下的3个基本性质:

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符;
  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
  • 每个节点的所有子节点包含的字符都不相同。

比如sher在字典树中的表示为:

数据结构之字典树的分析与实现_第1张图片

对于字典树,其常用的操作为插入查找,较少进行删除操作,其中查找又分为查找键(字符串)查找键(字符串)前缀

字典树的实现

力扣的108题即为实现字典树,主要是要实现上面提到的字典树的三个常用操作,即:

  • 向字典树中插入一个键:从根开始搜索它对应于第一个键字符的链接,会出现两种情况:

    • 链接存在。沿着链接移动到树的下一个子层,继续搜索下一个键字符;
    • 链接不存在。创建一个新的节点,并将它与父节点的链接相连,该链接与当前的键字符相匹配,新创建的节点即包含该键字符。

    重复以上步骤,直到到达键的最后一个字符,然后将当前节点标记为结束节点即isEnd = true,算法完成。

  • 在字典树中查找键:每个键在trie中表示为从根到内部节点或叶的路径。用第一个键字符从根开始,检查当前节点中与键字符对应的链接。有两种情况:

    • 存在链接。移动到该链接后面路径中的下一个节点,并继续搜索下一个键字符;
    • 不存在链接。若已无键字符,且当前结点标记为 isEnd = true,则返回 true。否则返回 false (可能还有键字符剩余,但无法跟随Trie树的键路径,即找不到键;也可能没有键字符剩余,但当前节点标记为isEnd = false,这时要查找的键只是另一个键的前缀)。
  • 在字典树中查找键的前缀:与查找键几乎一样,唯一的区别是,到达键前缀的末尾时,总是返回 true,而不需再判断是否为另一个键的前缀。

class Trie {

    private boolean isEnd;
    private Trie[] next;
    private final int R = 26;

    /** Initialize your data structure here. */
    public Trie() {
        this.next = new Trie[R];
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            char currChar = word.charAt(i);
            if (node.next[currChar - 'a'] == null) {
                node.next[currChar - 'a'] = new Trie();
            }
            node = node.next[currChar - 'a'];
        }
        node.isEnd = true;
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            char currChar = word.charAt(i);
            if (node.next[currChar - 'a'] == null) {
                return false;
            }
            node = node.next[currChar - 'a'];
        }
        return node.isEnd;  
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            char currChar = prefix.charAt(i);
            if (node.next[currChar - 'a'] == null) {
                return false;
            }
            node = node.next[currChar - 'a'];
        }
        return true;
    }
}

字典树和其他数据结构的比较

其他数据结构,如平衡树和哈希表,也能够在字符串数据集中搜索单词。但是尽管哈希表可以在 O(1) 时间内寻找键值,却无法高效的完成以下操作:

  • 找到具有同一前缀的全部键值;
  • 按词典序枚举字符串的数据集。

哈希表还有另一个缺点,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到 O(n),其中 n 是插入的键的数量。与哈希表相比,字典树在存储多个具有相同前缀的键时可以使用较少的空间。此时字典树树只需要O(m) 的时间复杂度,其中 m 为键长。而在平衡树中查找键值需要O(m·logn) 时间复杂度

你可能感兴趣的:(算法,数据结构)