Trie树|前缀树的介绍与Java实现

5,766 阅读2分钟

什么是Trie树

Trie树,又叫字典树、前缀树(Prefix Tree)、单词查找树 或 键树,是一种多叉树结构。

Trie树的优缺点

优点

  • 插入和查询的效率很高,都为O(m)的时间复杂度,其中 m 是待插入/查询的字符串的长度。
  • 关于查询,会有人说 hash 表时间复杂度是O(1)不是更快?但是,哈希搜索的效率通常取决于 hash 函数的好坏,若一个坏的 hash 函数导致很多的冲突,效率并不一定比Trie树高。
  • Trie树中不同的关键字不会产生冲突。
  • Trie树只有在允许一个关键字关联多个值的情况下才有类似hash碰撞发生。
  • Trie树不用求 hash 值,对短字符串有更快的速度。通常,求hash值也是需要遍历字符串的
  • Trie树可以对关键字按字典序排序。

缺点

  • 当 hash 函数很好时,Trie树的查找效率会低于哈希搜索。
  • 空间消耗比较大。

Trie树的应用

  • 字符串检索
  • 词频统计
  • 字符串排序
  • 前缀匹配
  • 作为其他数据结构和算法的辅助结构,如后缀树,AC自动机等。

Trie树的java实现

思想

  • 多岔树结构。
  • 连接线是字符。
  • 节点有两个属性,一个是字符经过的次数,一个是字符结尾的次数。 比如加入字符串 abc ab ack.

图解

image.png

代码

public static class Node{
        public int pass;
        public int end;
        public HashMap<Integer, Node> nexts;
        public Node(){
            pass =0;
            end =0;
            nexts = new HashMap<>();
        }
    }

    public static class Trie{
        private Node root;
        public Trie(){
            root =  new Node();
        }

        public void insert(String word){
            if (word == null){
                return;
            }
            char[] chars = word.toCharArray();
            Node node = root;
            node.pass++;
            int index = 0;//路径
            for (int i= 0; i<chars.length;i++){
                index = (int) chars[i];
                if (!node.nexts.containsKey(index)){//下一层的路径中不包括
                    node.nexts.put(index,new Node());
                }
                node = node.nexts.get(index);//当前节点
                node.pass++;//当前节点++
            }
            //循环结束后,说明单词已经遍历完
            node.end++;
        }

        public void delete(String word){
            if (serch(word)!=0){
                Node node = root;
                int index = 0;
                char[] chars = word.toCharArray();
                for (int i=0;i<chars.length;i++){
                    index = (int)chars[i];
                    if (--node.nexts.get(index).pass == 0){
                        node.nexts.remove(index);//删除节点,防止内存泄露
                    }
                    node = node.nexts.get(index);
                }
                node.end--;
            }
        }

        //word 这个单词之前加入了几次
        private int serch(String word) {
            if (word == null){
                return 0;
            }
            char[] chars = word.toCharArray();
            Node node = root;
            int index = 0;
            for (int i= 0; i<chars.length;i++) {
                index = (int) chars[i];
                if (!node.nexts.containsKey(index)){
                    return 0;
                }
                node = node.nexts.get(index);
            }
            return node.end;//有几个结尾,就加入了几次
        }

        //所有加入的字符串中,有几个是以pre 这个字符串作为前缀的
        public int prefixNumber(String pre){
            if (pre == null){
                return 0;
            }
            char[] chars = pre.toCharArray();
            Node node = root;
            int index =0;
            for (int i=0;i<chars.length;i++){
                if (!node.nexts.containsKey(index)){
                    return 0;
                }
                node = node.nexts.get(index);
            }
            return node.pass;//有几个单词经过 就有几个
        }

    }