yifanzheng / leetcode-java

数据结构与算法学习
MIT License
3 stars 0 forks source link

Trie 树 #18

Open yifanzheng opened 2 years ago

yifanzheng commented 2 years ago

Trie 树

Trie 树,也叫“字典树”、“前缀树”。它是一个树形结构,一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。

假设我们有 6 个字符串,它们分别是:how,hi,her,hello,so,see。我们希望在里面多次查找某个字符串是否存在。如果每次查找,都是拿要查找的字符串跟这 6 个字符串依次进行字符串匹配,那效率就比较低,有没有更高效的方法呢?

这个时候,我们就可以先对这 6 个字符串做一下预处理,组织成 Trie 树的结构,之后每次查找,都是在 Trie 树中进行匹配查找。Trie 树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起

Trie 树示例

其中,根节点不包含任何信息。每个节点表示一个字符串中的字符,从根节点到红色节点的一条路径表示一个字符串。

实现一棵 Trie 树

Trie 树主要有两个操作,一个是将字符串集合构造成 Trie 树,即将一个将字符串插入到 Trie 树的过程。另一个是在 Trie 树中查询一个字符串

示例:构建一个字符串只有 a-z 的 26 个小写字母的 Trie 树,我们在数组中下标为 0 的位置,存储指向子节点 a 的指针,下标为 1 的位置存储指向子节点 b 的指针,以此类推,下标为 25 的位置,存储的是指向的子节点 z 的指针。如果某个字符的子节点不存在,我们就在对应的下标的位置存储 null。

public class Trie {
    /**
     * 构建 Trie 树根节点
     */
    private TrieNode root = new TrieNode('/');

    public void insert(char[] str) {
        TrieNode p = root;
        for (int i = 0; i < str.length; i++) {
            int index = str[i] - 'a';
            if (p.chilren[index] == null) {
                TrieNode trieNode = new TrieNode(str[i]);
                p.chilren[index] = trieNode;
            }
            p = p.chilren[index];
        }
        p.isEndChar = true;
    }

    public boolean find(char[] str) {
        TrieNode p = root;
        for (int i = 0; i < str.length; i++) {
            int index = str[i] - 'a';
            if (p.chilren[index] == null) {
                return false;
            }
            p = p.chilren[index];
        }
        // 如果节点不是最后的字符,不能完全匹配,是前缀匹配
        return p.isEndChar;
    }

    private static class TrieNode {
        private char data;
        private TrieNode[] chilren = new TrieNode[26];
        private boolean isEndChar = false;

        public TrieNode(char data) {
            this.data = data;
        }
    }

}

构建 Trie 树的过程,需要扫描所有的字符串,时间复杂度是 O(n)(n 表示所有字符串的长度和)。但是一旦构建成功之后,后续的查询操作会非常高效。

每次查询时,如果要查询的字符串长度是 k,那我们只需要比对大约 k 个节点,就能完成查询操作。跟原本那组字符串的长度和个数没有任何关系。所以说,构建好 Trie 树后,在其中查找字符串的时间复杂度是 O(k),k 表示要查找的字符串的长度。

小结

Trie 树是非常耗内存的,用的是一种空间换时间的思路,它对要处理的字符串有及其严苛的要求。

综上,Trie 树只是不适合精确匹配查找,这种问题更适合用散列表或者红黑树来解决,而 Trie 树比较适合的是查找前缀匹配的字符串。