【恋上数据结构】前缀树 Tire 学习笔记

news2024/11/25 18:58:40

Tire

需求分析

如何判断一堆不重复的字符串是否以某个前缀开头?

  • Set\Map 存储字符串(不重复)
  • 遍历所有字符串进行判断
  • 缺点:时间复杂度 O(n)

有没有更优的数据结构实现前缀搜索?

Tire(和 Tree 同音)

简介

  • Trie 也叫做字典树、前缀树 (Prefix Tree)、单词查找树。
  • Trie 搜索字符串的效率主要跟字符串的长度有关。

假设使用 Trie 存储 catdogdoggydoescastadd 六个单词,结果如下所示

在这里插入图片描述

接口设计

有两种设计方案:

  1. 第一种仅仅是存储字符串。(像 set 集合)
  2. 第二种是存储字符串的同时可以再存储一个 value(像 map 接口)

分析:

第二种设计方案更为通用,比如说我们要做一个通讯录,以某个人的姓名作为 key,然后以他的详细信息作为 value(其他电话号码、邮箱、生日等各种详细信息)

public interface Trie <V> {
	int size(); 
	boolean isEmpty(); 
	void clear(); 
	boolean contains(String str); 
	V add(String str, V value); 
	V remove(String str); 
	boolean starswith(String prefix);
}

在这里插入图片描述

Node 设计

孩子节点集合解析(HashMap<Character, Node<V>> children;):

  • key 相当于代表的是路径值,Character 字符类型可以是英文也可以是中文
  • value 是嵌套了当前节点下的所有子节点,方便后面节点值寻找
  • word:true 为已存储单词(红色),false 为非单词(蓝色)
    /**
     * Trie 中的节点类,包含父节点、孩子节点集合、字符、值以及表示是否为一个完整单词的标志。
     *
     * @param <V> 值的类型
     */
    private static class Node<V> {
        Node<V> parent; // 父节点
        HashMap<Character, Node<V>> children; // 孩子节点集合
        Character character; // 字符,为删除做准备
        V value; // 节点对应的值,也就是整个单词
        boolean word; // 是否为单词的结尾(是否为一个完整的单词)

        /**
         * 构造函数,初始化节点时需要指定父节点。(在添加节点时用到)
         *
         * @param parent 父节点
         */
        public Node(Node<V> parent) {
            this.parent = parent;
        }
    }

完整代码实现附注释

/**
 * Trie(字典树)数据结构,用于存储字符串集合,支持添加、查询、删除等操作。
 *
 * @param <V> 值的类型
 */
public class Trie<V> {

    /** Trie 中存储的单词数量 */
    private int size;

    /** 根节点 */
    private Node<V> root;

    /**
     * Trie 中的节点类,包含父节点、孩子节点集合、字符、值以及表示是否为一个完整单词的标志。
     *
     * @param <V> 值的类型
     */
    private static class Node<V> {
        Node<V> parent; // 父节点
        HashMap<Character, Node<V>> children; // 孩子节点集合
        Character character; // 字符,为删除做准备
        V value; // 节点对应的值,也就是整个单词
        boolean word; // 是否为单词的结尾(是否为一个完整的单词)

        /**
         * 构造函数,初始化节点时需要指定父节点。(在添加节点时用到)
         *
         * @param parent 父节点
         */
        public Node(Node<V> parent) {
            this.parent = parent;
        }
    }

    /**
     * 获取 Trie 中存储的单词数量。
     *
     * @return Trie 中存储的单词数量
     */
    public int size() {
        return size;
    }

    /**
     * 判断 Trie 是否为空。
     *
     * @return 如果 Trie 为空,则返回 true;否则返回 false
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 清空 Trie,将单词数量重置为 0。
     */
    public void clear() {
        size = 0;
        root = null;
    }

    /**
     * 根据指定的键获取对应的值。
     *
     * @param key 键
     * @return 如果键存在且是一个完整的单词,则返回对应的值;否则返回 null
     */
    public V get(String key) {
        Node<V> node = node(key);
        return (node != null && node.word) ? node.value : null;
    }

    /**
     * 判断 Trie 是否包含指定的键。
     *
     * @param key 键
     * @return 如果 Trie 包含指定的键且是一个完整的单词,则返回 true;否则返回 false
     */
    public boolean contains(String key) {
        Node<V> node = node(key);
        return node != null && node.word;
    }

    /**
     * 添加键值对到 Trie 中。如果键已经存在,则更新对应的值;否则新增一个单词。
     *
     * @param key   键
     * @param value 值
     * @return 如果添加的键已经存在,则返回对应的旧值;否则返回 null
     */
    public V add(String key, V value) {
        keyCheck(key);

        // 创建根节点
        if (root == null) {
            root = new Node<>(null);
        }

        // 获取 Trie 根节点
        Node<V> node = root;
        // 获取键的长度
        int len = key.length();
        // 遍历键的每个字符
        for (int i = 0; i < len; i++) {
            // 获取当前字符
            char c = key.charAt(i);
            // 判断当前节点的孩子节点集合是否为空
            boolean emptyChildren = (node.children == null);
            // 获取当前字符对应的孩子节点
            Node<V> childNode = emptyChildren ? null : node.children.get(c);
            // 如果当前字符对应的孩子节点为空,说明该字符在当前节点的孩子节点集合中不存在
            if (childNode == null) {
                // 创建新的孩子节点,并将其加入到当前节点的孩子节点集合中
                childNode = new Node<>(node);
                childNode.character = c;
                // 判断孩子节点集合是否为空的同时,避免了每次都要创建新的 HashMap 对象,提高了效率
                node.children = emptyChildren ? new HashMap<>(16) : node.children;
                node.children.put(c, childNode);
            }
            // 将当前节点移动到其对应的孩子节点上,继续下一层的遍历
            node = childNode;
        }

        // 1 - 已经存在这个单词, 覆盖, 返回旧值
        if (node.word) {
            V oldValue = node.value;
            node.value = value;
            return oldValue;
        }

        // 2 - 不存在这个单词, 新增这个单词
        node.word = true;
        node.value = value;
        size++;
        return null;
    }

    /**
     * 移除 Trie 中的指定键。如果键存在且是一个完整的单词,将其从 Trie 中移除。
     *
     * @param key 键
     * @return 如果键存在且是一个完整的单词,则返回对应的值;否则返回 null
     */
    public V remove(String key) {
        Node<V> node = node(key);
        
        // 如果不是单词结尾,不用作任何处理
        if (node == null || !node.word) {
            return null;
        }
        size--;
        V oldValue = node.value;

        // 如果还有子节点
        if (node.children != null && !node.children.isEmpty()) {
            node.word = false;
            node.value = null;
            return oldValue;
        }

        // 没有子节点
        Node<V> parent = null;
        while ((parent = node.parent) != null) {
            parent.children.remove(node.character);
            if (parent.word || !parent.children.isEmpty()) {
                break;
            }
            node = parent;
        }
        return oldValue;
    }

    /**
     * 判断 Trie 是否包含指定前缀。
     *
     * @param prefix 前缀
     * @return 如果 Trie 包含指定前缀,则返回 true;否则返回 false
     */
    public boolean startsWith(String prefix) {
        return node(prefix) != null;
    }

    /**
     * 根据传入字符串,找到最后一个节点。
     * 例如输入 dog
     * 找到 g
     *
     * @param key 键
     * @return 如果键存在,则返回对应的节点;否则返回 null
     */
    private Node<V> node(String key) {
        keyCheck(key);

        Node<V> node = root;
        int len = key.length();
        for (int i = 0; i < len; i++) {
            if (node == null || node.children == null || node.children.isEmpty()) {
                return null;
            }
            char c = key.charAt(i);
            node = node.children.get(c);
        }
        return node;
    }

    /**
     * 检查键是否合法,不允许为空。
     *
     * @param key 键
     */
    private void keyCheck(String key) {
        if (key == null || key.length() == 0) {
            throw new IllegalArgumentException("key must not be empty");
        }
    }
}

总结

  1. Trie 的优点:搜索前缀的效率主要跟前缀的长度有关

  2. Trie 的缺点:需要耗费大量的内存(一个字符一个节点),因此还有待改进

  3. 更多 Trie 相关的数据结构和算法

    • Double-array Trie、Suffix Tree(后缀树)、Patricia Tree、Crit-bit Tree、AC 自动机

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1287601.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 和 macOS 的主要区别在哪几个方面呢?

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

Hadoop学习笔记(HDP)-Part.08 部署Ambari集群

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

Unity-Shader - 2DSprite描边效果

实现一个简单的2D精灵图描边效果&#xff0c;效果如下 实现思路&#xff1a; 可以通过判断该像素周围是否有透明度为 0的值&#xff0c;如果有&#xff0c;则说明该像素位于边缘。 所以我们需要打开alpha blend&#xff0c;即&#xff1a; Blend SrcAlpha OneMinusSrcAlpha&am…

腾讯云轻量应用服务器怎么使用宝塔面板?

腾讯云轻量应用服务器宝塔面板怎么用&#xff1f;轻量应用服务器如何安装宝塔面板&#xff1f;在镜像中选择宝塔Linux面板腾讯云专享版&#xff0c;在轻量服务器防火墙中开启8888端口号&#xff0c;然后远程连接到轻量服务器执行宝塔面板账号密码查询命令&#xff0c;最后登录和…

Apache或Nginx在Linux上配置虚拟主机

在Linux上使用Apache或Nginx配置虚拟主机可以让您在同一台服务器上托管多个网站。这样不仅可以充分利用服务器资源&#xff0c;还能降低每个网站的运营成本。以下是使用Apache和Nginx配置虚拟主机的步骤。 使用Apache配置虚拟主机 安装Apache服务器软件。在终端中使用以下命令…

网络安全(三)-- 网络嗅探及协议分析技术

目标 了解网络嗅探的基本含义了解tcpdump工具的基本用法掌握tcpdump工具抓包保存到文件的方法熟悉wireshark工具的基本用法掌握借助wireshark抓包工具分析简单网络协议的方法 6.1. 概述 网络嗅探是一种常用的数据收集、分析的方法: 黑客常通过网络嗅探获取主机或网络的控制权…

资料分析(花生)

基期A&#xff08;给出BR或BX&#xff09; 前期&#xff1a;代入、直除、假设分配隔年前期&#xff1a;求出间隔增长率&#xff0c;再变成第一类考法前期差值&#xff1a;假设分配法求得两个前期作差。 现期B 有增量求现期&#xff1a;求出 X&#xff0c;列不等式即可有增速求现…

uniapp 使用 $emit和$on——$on中无法为data中的变量赋值

问题在于this的指向&#xff0c; 解决办法是使用变量保存$on&#xff0c;其次再为data中的值赋值 以下是具体代码&#xff1a; 1、html代码&#xff1a; <view class"form_picker" click"selePositionFun()"><view class""><inp…

Leetcode 77 组合

题意理解&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 如&#xff1a;n3,k2,则有&#xff1a;12 13 23 一般&#xff0c;我们使用回溯法来解决组合问题。 组合问题没有顺序要求&#xff0c;所以 12 21 是同一个组合&#xff08;如…

centos7安装Elasticsearch7系列

背景 今天公司项目需要使用Elasticsearch7.17.7。所有网上搜索了一番&#xff0c;查到一个很不错安装方式分享给大家。 Elasticsearch官网发布 从 Elasticsearch 7.x 版本开始&#xff0c;Elasticsearch 发行版包括了自己的 JDK。因此&#xff0c;您不需要单独安装 Java。以…

Android View.inflate 和 LayoutInflater.from(this).inflate的区别

前言 两个都是布局加载器&#xff0c;而View.inflate是对 LayoutInflater.from(context).inflate的封装&#xff0c;功能相同&#xff0c;案例使用了dataBinding。 View.inflate(context, layoutResId, root) LayoutInflater.from(context).inflate(layoutResId, root, fals…

CLIP在Github上的使用教程

CLIP的github链接&#xff1a;https://github.com/openai/CLIP CLIP Blog&#xff0c;Paper&#xff0c;Model Card&#xff0c;Colab CLIP&#xff08;对比语言-图像预训练&#xff09;是一个在各种&#xff08;图像、文本&#xff09;对上进行训练的神经网络。可以用自然语…

JS箭头函数

箭头函数 1. 基本语法 // // 一般函数const fn function() {console.log(123);}// 箭头函数const fn () > {console.log(123);}fn()const fn (x) > {console.log(x);}fn(1)// 只有一个形参的时候可以省略小括号const fn x > {console.log(x);}fn(1)// 只有一行代…

基于c++版本链队列改-Python版本链队列基础理解

##基于链表的队列实现 可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”&#xff0c;规定队尾仅可添加节点&#xff0c;队首仅可删除节点。 ##图解 ##基于链表的队列实现代码 class ListNode:"""定义链表"""def __init__(self)…

nodejs微信小程序+python+PHP本科生优秀作业交流网站的设计与实现-计算机毕业设计推荐

通过软件的需求分析已经获得了系统的基本功能需求&#xff0c;根据需求&#xff0c;将本科生优秀作业交流网站功能模块主要分为管理员模块。管理员添加系统首页、个人中心、用户管理、作业分类管理、作业分享管理、论坛交流、投诉举报、系统管理等操作。 随着信息化社会的形成…

Mybatis XML 配置文件

我们刚开始就有说Mybatis 的开发有两种方式: 1.注释 2.XML 注解和 XML 的方式是可以共存的 我们前面说的都是注释的方式,接下来是XML方式 XML的方式分为三步 : 1.配置数据库(配在 application.yml 里面) 这个跟注释的配置是一样的,username应该都是一样的,password记得写…

git常用命令指南

目录 一、基本命令 1、创建分支 2、切换分支 3、合并分支 4、初始化空git仓库 二、文件操作 1、创建文件 2、添加多个文件 3、查看项目的当前状态 4、修改文件 5、删除文件 6、提交项目 三、实际操作 1、创建目录 2、进入新目录 3、初始化空git仓库 4、创建文…

【OpenGauss源码学习 —— (RowToVec)算子】

VecToRow 算子 概述ExecInitRowToVec 函数ExecRowToVec 函数VectorizeOneTuple 函数 ExecEndRowToVec 函数总结 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵循合理使用原则&#xff0c;并在…

使用AWS Glue与AWS Kinesis构建的流式ETL作业(二)——数据处理

大纲 2 数据处理2.1 架构2.2 AWS Glue连接和创建2.2.1 创建AWS RedShift连接2.2.2 创建AWS RDS连接&#xff08;以PG为例&#xff09; 2.3 创建AWS Glue Job2.4 编写脚本2.4.1 以AWS RedShift为例2.4.2 以PG为例 2.5 运行脚本 2 数据处理 2.1 架构 2.2 AWS Glue连接和创建 下…

DSSS技术和OFDM技术

本内容为学习笔记&#xff0c;内容不一定正确&#xff0c;请多处参考进行理解 https://zhuanlan.zhihu.com/p/636853588 https://baike.baidu.com/item/OFDM/5790826?frge_ala https://zhuanlan.zhihu.com/p/515701960?utm_id0 一、 DSSS技术 信号替代&#xff1a;DSSS技术为…