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

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.mzph.cn/news/210389.shtml

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

相关文章

Rust测试字符串的移动,Move

代码创建了一个结构体&#xff0c;结构体有test1 字符串&#xff0c;还有指向字符串的指针。一共创建了两个。 然后我们使用swap 函数 交换两个结构体内存的内容。 最后如上图。相同的地址&#xff0c;变成了另外结构体的内容。注意看指针部分&#xff0c;还是指向原来的地址…

input、el-input输入框输入规则

一、input 只能输入框只能输入正整数&#xff0c;输入同时禁止了以0开始的数字输入&#xff0c;防止被转化为其他进制的数值。 <!-- 不能输入零时--> <input typetext οninput"valuevalue.replace(/^(0)|[^\d]/g,)"><!-- 能输入零时--> <inp…

luceda ipkiss教程 43:画渐变圆弧型波导

案例分享&#xff1a; from si_fab import all as pdk import ipkiss3.all as i3 from ipcore.properties.restrictions import RestrictTuple from ipkiss.geometry.shapes.modifiers import __ShapePathBase__ import numpy as np from math import atan2class ShapePathTa…

[java]学生管理系统

一、学生类 首先创建一个学生类&#xff0c;定义学号姓名年龄居住地 public class Student {private String id;private String name;private String age;private String address;//构造函数public Student(String id, String name, String age, String address) {this.id i…

54.grpc实现文件上传和下载

文章目录 一&#xff1a;简介1. 什么是grpc2. 为什么我们要用grpc 二&#xff1a;grpc的hello world1、 定义hello.proto文件2、生成xxx_grpc.pb.go文件3、生成xxx.pb.go结构体文件4、编写服务代码service.go5、编写客户端代码client.go 三、服务端流式传输&#xff1a;文件下载…

AIOps、微服务和云平台

数字景观正在从整体转向微服务、基于云的服务。企业和公司需要适应不断变化的技术格局并跟上变化。系统变得越来越复杂并且不容易管理。我将尝试解释一些较新的架构方法、趋势&#xff0c;并提供对 AIOps 的见解以及它如何帮助解决这个问题。 微服务 微服务架构正在成为最受欢…

什么是web组态?一文读懂web组态

随着工业4.0的到来&#xff0c;物联网、大数据、人工智能等技术的融合应用&#xff0c;使得工业领域正在经历一场深刻的变革。在这个过程中&#xff0c;web组态技术以其独特的优势&#xff0c;正在逐渐受到越来越多企业的关注和认可。那么&#xff0c;什么是web组态&#xff1f…

android-android源码目录

android源码目录 Android.bp art bionic bootable bootstrap.bash build build.sh compatibility cts dalvik developers development device external frameworks hardware IMAGE javaenv.sh kernel libcore libnativehelper Makefile mkcombinedroot mkimage_ab.sh mkimage.…

我的创作纪念日——一年

机缘 初心始于对技术的热爱和分享知识的渴望。最初&#xff0c;我在一次练习中遇到了一些问题&#xff0c;通过解决这些问题并将解决方案记录下来&#xff0c;我意识到分享经验对自己和他人都非常有价值。于是&#xff0c;我开始在博客和社交平台上记录日常学习过程、撰写技术…

uni-app 获取PAD激光测温方式 (uni-app安卓获取广播内容)

直接在onload执行下列代码 var main plus.android.runtimeMainActivity(); //获取activityvar context plus.android.importClass(android.content.Context); //上下文var receiver plus.android.implements(io.dcloud.feature.internal.reflect.BroadcastReceiver, {onRece…

动力未来:特斯拉 Model S 电池技术一览

电动汽车是当今最具创新性和前景的交通工具之一,它们不仅能够提供高效、环保的驾驶体验,还能够减少对化石燃料的依赖,促进可持续发展。在电动汽车领域,特斯拉 Model S 是一款引领潮流的产品,它以其豪华、强劲的性能和尖端的电池技术而闻名。本文将为您介绍特斯拉 Model S …

【springboot设计源码】庆阳非物质文化遗产展示平台课题背景、目的、意义、研究方法

该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等学习内容。 目录 一、项目介绍&#xff1a; 二、文档学习资料&#xff1a; 三、模块截图&#xff1a; 四、开发技术与运行环境&#xff1a; 五、代码展示&#xff1a; 六、数据库表截图&#xff1…

即时通讯技术文集(第26期):实时音视频技术合集(Part1) [共16篇]

为了更好地分类阅读 52im.net 总计1000多篇精编文章&#xff0c;我将在每周三推送新的一期技术文集&#xff0c;本次是第26 期。 [- 1 -] 实时语音聊天中的音频处理与编码压缩技术简述 [链接] http://www.52im.net/thread-825-1-1.html [摘要] 在视频或者音频通话过程中&…

2023-12-09 LeetCode每日一题(下一个更大的数值平衡数)

2023-12-09每日一题 一、题目编号 2048. 下一个更大的数值平衡数二、题目链接 点击跳转到题目位置 三、题目描述 如果整数 x 满足&#xff1a;对于每个数位 d &#xff0c;这个数位 恰好 在 x 中出现 d 次。那么整数 x 就是一个 数值平衡数 。 给你一个整数 n &#xff0…

数据结构和算法专题---4、限流算法与应用

本章我们会对限流算法做个简单介绍&#xff0c;包括常用的限流算法&#xff08;计数器、漏桶算法、令牌桶案发、滑动窗口&#xff09;的概述、实现方式、典型场景做个说明。 什么是限流算法 限流是对系统的一种保护措施。即限制流量请求的频率&#xff08;每秒处理多少个请求…

11_企业架构web服务器文件及时同步

企业架构web服务器的文件及时同步 学习目标和内容 1、能够理解为何要服务器间文件同步 2、能够简单描述实现文件同步的几种方式 3、能够实现服务器文件实时同步的案例 一、同步文件介绍 1、服务器文件同步的必要性 根据业务发展需求&#xff0c;业务网站架构已经发展到以上模式…

Linux文件结构与文件权限

基于centos了解Linux文件结构 了解一下文件类型 Linux采用的一切皆文件的思想&#xff0c;将硬件设备、软件等所有数据信息都以文件的形式呈现在用户面前&#xff0c;这就使得我们对计算机的管理更加方便。所以本篇文章会对Linux操作系统的文件结构和文件权限进行讲解。 首先…

单元测试Nunit的几种断言

Nunit提供了一些辅助函数用于确定好某个被测试函数是否正常工作。通常把这些函数称为断言 断言是单元测试最基本的组成部分。因此&#xff0c;NUnit程序库以Assert类的静态方法的形式提供了不同形式的多种断言 1. Assert.AreEqual&#xff1a;比较两个值是否相等。用于比较数…

Qt生成动态链接库并使用动态链接库

项目结构 整个工程由一个主程序构成和一个模块构成(dll)。整个工程的结构目录如下 Define.priMyProject.proMyProject.pro.user ---bin ---MainProgrammain.cppMainProgram.proMainProgram.pro.userwidget.cppwidget.hwidget.ui ---MathDllMathDll.proMathDll.pro.userMyMath.…

Axios 拦截器实战教程:简单易懂

Axios 提供了一种称为 “拦截器&#xff08;interceptors&#xff09;” 的功能&#xff0c;使我们能够在请求或响应被发送或处理之前对它们进行全局处理。拦截器为我们提供了一种简洁而强大的方式来转换请求和响应、进行错误处理、添加认证信息等操作。在本文中&#xff0c;我…