字典树原理与应用

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

字典树原理与应用


一、概念


字典树又称单词查找树,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]


五、应用


给定一个单词列表,我们将这个列表编码成一个索引字符串 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;
    }
}


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


分类: 数据结构与算法

相关文章
|
人工智能 API 决策智能
Modelscope结合α-UMi:基于Modelscope的多模型协作Agent
基于单个开源小模型的工具调用Agent,由于模型容量和预训练能力获取的限制,无法在推理和规划、工具调用、回复生成等任务上同时获得比肩大模型等性能。
|
4月前
|
监控 Java 测试技术
Spring Boot学习知识点大全(三)
教程来源 https://app-a6nw7st4g741.appmiaoda.com/ 系统梳理Spring Boot核心实践:涵盖日志分级配置与异步输出、单元/集成测试、Actuator监控与自定义指标、Docker/K8s部署、Spring Boot 3.x Jakarta迁移及虚拟线程等新特性,助力构建高可用生产级应用。
|
2月前
|
人工智能 运维 安全
Windows10用户部署OpenClaw的终极指南|路径规范+权限配置+故障排查
专为Windows 10 64位深度优化的OpenClaw(小龙虾)一键部署包:免命令行、免环境配置,解压即装;内置全部依赖与28万Tokens,全程可视化操作;独家解决SmartScreen拦截、权限限制等Win10特有问题,新手也能一次成功“养虾”!
|
5月前
|
存储 人工智能 弹性计算
🔥🔥阿里云优惠活动大全:2026年最新整理云服务器、免费、AI大模型活动都有
2026年阿里云新春优惠全面开启:轻量服务器38元/年起,ECS 99元/年续费同价;学生享300元无门槛券,企业可领5亿算力补贴;百炼平台免费领7000万tokens,建站送CN域名,160+云产品免费试用!
506 4
|
前端开发 JavaScript Java
一个软件开发工程师需要学几种编程语言?为什么?
一个软件开发工程师需要学几种编程语言?为什么?
1038 64
|
存储 安全 数据安全/隐私保护
深入探索Android与iOS的隐私保护机制:一场没有硝烟的较量####
本文深度剖析了Android与iOS两大移动操作系统在用户隐私保护方面的策略与实践,揭示两者在设计理念、技术实现及用户体验上的异同。通过对比分析,旨在为读者提供一个全面而深入的视角,理解两大平台如何在保障用户隐私的同时,实现功能的丰富与便捷。本文不涉及具体产品推荐或品牌偏好,仅从技术角度出发,探讨隐私保护的现状与挑战。 ####
|
网络协议 网络安全 API
Http和Socks的区别?
HTTP 和 SOCKS 协议各有其优势和应用场景。在选择使用哪种协议时,应根据具体需求和应用环境做出决定。HTTP 适用于 Web 服务相关的通信,而 SOCKS 则更适用于需要通用代理功能和复杂网络环境的场景。了解它们的区别和特点,有助于在不同的网络应用中做出最佳选择。
860 1
|
前端开发 JavaScript Java
技术分享:使用Spring Boot3.3与MyBatis-Plus联合实现多层次树结构的异步加载策略
在现代Web开发中,处理多层次树形结构数据是一项常见且重要的任务。这些结构广泛应用于分类管理、组织结构、权限管理等场景。为了提升用户体验和系统性能,采用异步加载策略来动态加载树形结构的各个层级变得尤为重要。本文将详细介绍如何使用Spring Boot3.3与MyBatis-Plus联合实现这一功能。
562 2
|
机器学习/深度学习 数据采集 自然语言处理
LangChain
【7月更文挑战第30天】
390 4
|
存储 固态存储 定位技术
如何选择移动存储设备
【10月更文挑战第6天】选择移动存储设备需考虑多个因素,包括存储容量、读写速度、接口类型、设备类型及数据安全。容量应根据需求评估,留有余量;读写速度影响传输效率,USB 3.0 及以上接口更佳;设备类型有U盘、移动硬盘等,各具特色;数据加密和品牌质量保证则提升数据安全性。
934 0

热门文章

最新文章