字典树

412 阅读2分钟

字典树

1.1 基本数据结构

数据结构大都离不开数组链表。他们是计算机中最基本的数据机构,基本所有的数据机构都可以有这两种数据机构来实现。

而链表是一种特殊的树,树更是一种特殊的图。

极客时间 数学专栏的作者的一段话,一直觉得很受用:

其实计算机到了最后还是搞数学,我们要做的就是如何将问题应用到数学在到编程上去。

数学思想到编程的实现大都分为三步走

  • 把问题转化为数学中的模型
  • 用数据结构与算法来刻画数学模型
  • 落实编码

1.2 字典树

1.2.1 什么是字典树?

字典树也可以叫做前缀树,其实就是将很多单词组成的一种数据机构。通常都是树形结构的所有叫做字典树。

如下图所示:

跟节点通常都是空,不同的单词组成一条条的通路,形成了一个树状结构。

字典树有两种实现方式链表数组

因为字典树的二维关系是非常稀疏非常动态的,所以采用链表来实现字典树。

具体的代码注释如下:

 static class TreeNode{

        /**
         * 节点的数据void
         */
        private char data;

        /**
         * 子节点
         */
        private Map<Character,TreeNode>  sons = null;

        /**
         * 节点的解释
         */
        private String explanation = null;

        /**
         * 节点的前缀字符串
         */
        private String prefix = null;


        public TreeNode(char data, String explanation, String prefix) {
            this.data = data;
            this.sons = new HashMap<>(16);
            this.explanation = explanation;
            this.prefix = prefix;
        }
    }

1.2.2 字典树的操作

字典树有两种

因为字典树的二维关系是非常稀疏非常动态的,

字典树的操作有两个

  • 构建字典树(Dynamic)

    public static void buildPrefixTree(String word){
            if(StringUtils.isEmpty(word)) {
                return;
            }
    
            TreeNode findNode = headNode;
            String prefix = "";
            String explanation = null;
    
            for(int i=0;i<word.length();i++){
                char c = word.charAt(i);
                Map<Character, TreeNode> sons = findNode.sons;
                if(sons.containsKey(c)){
                    findNode = sons.get(c);
                }else{
                    if(i == word.length()-1){
                        explanation = prefix + c;
                    }
                    TreeNode sonNode = new TreeNode(c,explanation,prefix);
                    sons.put(c,sonNode);
                    findNode = sonNode;
                }
                prefix += c;
            }
        }
    
  • 查询字典

/**
     * 在PrefixTree中查找 单词
     * @param word
     * @return
     */
    public static TreeNode findWrod(String word){

        if(StringUtils.isEmpty(word)) {
            return null;
        }
        TreeNode findNode = headNode;

        for(int i=0;i<word.length();i++){
            char c = word.charAt(i);
            Map<Character, TreeNode> sons = findNode.sons;

            if( sons.size()!=0 && sons.containsKey(c)){
                findNode = sons.get(c);
            }else{
                return null;
            }
        }

        if(StringUtils.isEmpty(findNode.explanation)){
            return null;
        }

        return findNode;
    }
  • 遍历字典树中的所有单词

     /**
         * 使用栈便利所有的单词
         */
        public static void dfsByStack(){
            Stack<TreeNode>  stack = new Stack<>();
            stack.push(headNode);
            while(stack.size()>0){
                TreeNode node = stack.pop();
                if(!StringUtils.isEmpty(node.explanation)){
                    System.out.println(node.explanation);
                }
                Map<Character, TreeNode> sons = node.sons;
                sons.forEach((sonKey,sonValue)->{
                    stack.push(sonValue);
                });
            }
        }
    

字典树的这三种操作其实都可以用递归来实现,但是最后一种的效率是明显优于递归的,因为减少了大量的中间变量的创建,利用了栈模拟了递归调用。