字典树原理与应用

一、概念

字典树又称单词查找树,Trie树,是一种树形结构,是哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计、搜索联想等。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较。

二、特点

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

三、常见操作

查找、插入和删除(很少用到)。

四、实现

代码如下,这段代码实现了字典树的insert以及根据前缀匹配所有字符串的方法。

public class Trie {

    private TrieNode root;

    public Trie(){
        root = new TrieNode();
    }

    public void insert(String str){
        if(str==null||str.length()==0){
            return;
        }
        TrieNode node = root;
        char[] allChars = str.toCharArray();
        for(int i=0; i<allChars.length; i++){
            Character character = new Character(allChars[i]);
            if (!node.children.containsKey(character)){
                node.children.put(character, new TrieNode(character));
            }
            node = node.children.get(character);
        }
        node.isEnd = true;
    }

    public List<String> matchPrefix(String prefix){
        List<String> result = new ArrayList<String>();
        if(prefix==null||prefix.length()==0){
            return result;
        }
        char[] allChars = prefix.toCharArray();
        TrieNode node = root;
        for(int i=0; i<allChars.length; i++){
            Character character = new Character(allChars[i]);
            if(!node.children.containsKey(character)){
                return result;
            }else{
                node = node.children.get(character);
            }
        }
        preTraverse(node, prefix, result);
        return result;
    }

    private void preTraverse(TrieNode node, String prefix, List<String> result){
        if(!node.children.isEmpty()){
            for (Map.Entry<Character, TrieNode> entry: node.children.entrySet()){
                if (entry.getValue().isEnd){
                    result.add(prefix+entry.getKey().toString());
                }
                preTraverse(entry.getValue(), prefix+entry.getKey().toString(), result);
            }
        }
    }

    private class TrieNode {

        private Map<Character, TrieNode> children;

        private boolean isEnd;

        private Character character;

        TrieNode(){
            children = new HashMap<Character, TrieNode>();
            isEnd = false;
        }

        TrieNode(Character character){
            children = new HashMap<Character, TrieNode>();
            isEnd = false;
            this.character = character;
        }
    }
}

我们通过一段代码测试一下:

public class MainApp {

    public static void main(String[] argv){
        ArrayList<String> strs = new ArrayList<String>();
        strs.add("我爱学习");
        strs.add("我爱学");
        strs.add("我爱学JAVA");
        strs.add("我不爱学习");
        strs.add("小明也爱学习");
        strs.add("我爱Python");

        Trie trie = new Trie();
        for(String s: strs){
            trie.insert(s);
        }
        String prefix = "我爱";
        List<String> res = trie.matchPrefix(prefix);
        System.out.println(res);
    }
}

执行后输出:[我爱Python, 我爱学, 我爱学习, 我爱学JAVA]

五、应用

扫描二维码关注公众号,回复: 10295395 查看本文章

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

例如,如果这个列表是 ["time", "me", "bell"],我们就可以将其表示为 S = "time#bell#" 和 indexes = [0, 2, 5]。

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

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

示例:

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

通过分析,我们可以看出,这道题目标就是保留所有不是其他单词后缀的单词,最后的结果就是这些单词长度加一的总和(因为每个单词编码后后面还需要跟一个 # 符号)。很明显这里需要使用字典表。代码如下:

class Solution {
    public int minimumLengthEncoding(String[] words) {
        Trie trie = new Trie();
        HashMap<TrieNode, Integer> nodes = new HashMap<TrieNode,Integer>();
        
        for(int k=0; k<words.length; k++){
            String s = words[k];
            TrieNode node = trie.insert(s);
            if(node!=null){
                nodes.put(node, k);    
            }
        }
        int res = 0;
        for(TrieNode n: nodes.keySet()){
            if(n.children.isEmpty()){
                res += words[nodes.get(n)].length()+1;
            }
        }
        return res;
    }
}

class Trie {
    TrieNode root;
    Trie(){
        root = new TrieNode();
    }
    TrieNode insert(String str){
        if(str==null||str.length()==0){
            return null;
        }
        TrieNode node = root;
        char[] allChars = str.toCharArray();
        for(int i=allChars.length-1; i>=0; i--){
            Character character = new Character(allChars[i]);
            if (!node.children.containsKey(character)){
                node.children.put(character, new TrieNode(character));
            }
            node = node.children.get(character);
        }
        node.isEnd = true;
        return node;
    }
}

class TrieNode {
    Map<Character, TrieNode> children;
    boolean isEnd;
    Character character;
    TrieNode(){
        children = new HashMap<Character, TrieNode>();
        isEnd = false;
    }
    TrieNode(Character character){
        children = new HashMap<Character, TrieNode>();
        isEnd = false;
        this.character = character;
    }
}

这里为了方便最后的字符串统计,所以对本文前面提供的字典表代码略作修改,但是整体思路是一样的。

猜你喜欢

转载自www.cnblogs.com/blackwatch1206/p/12596060.html
今日推荐