数据结构之前缀树

写在前面

源码 。

前缀树,又叫做trie树,字典树,是一种多叉的树,一般用于单词前缀匹配的相关场景中,比如:
在这里插入图片描述
本文看下使用Java如何来实现这种数据结构。

1:基本介绍

思想:空间换时间,因为需要维护非常多的引用,所以比较占用空间,但能够快速定位所以时间较短
时间复杂度:log
特点:根节点不包含字符每一条路径所有节点的字符拼接在一起就对应一个字符串拥有相同前缀的多个字符串共享相同前缀

结构如下:
在这里插入图片描述

2:代码实现

定义节点类:

public class TreeNode {//经过这个节点的字符串的个数(以这个节点为前缀的字符串的个数)public int path;//以这个节点结束的字符串的个数(有多少个字符串有这条路径的char组成)public int end;//对应着小写的a-z的26个字母(如果要更多可以使用hashmap<char,Node>public TreeNode[] next;// 是否为叶子节点public boolean isLeaf = true;// 是否为一个单词的结束字符public boolean isWordEnd = false;public TreeNode() {path = 0;end = 0;next = new TreeNode[26];}@Overridepublic String toString() {return "TreeNode{" +"path=" + path +", end=" + end +", next=" + Arrays.toString(next) +'}';}
}

定义前缀树类:

public class TrieTree {public TreeNode root;public TrieTree() {root = new TreeNode();}/*** 在前缀树中插入字符串* 这种++的方法,导致,一个node,有多少个end,就有多少个相同的字符串* 一个node,有多少个path,就有多少个字符串经过(root的path代表有多少个字符串)(字符串末尾的node的path也会++)** @param string 被插入的字符串(以前插入过的也可以插入)*/public void insertString(String string) {if (string == null || string.length() == 0) {return;}int length = string.length();TreeNode nowNode = root;for (int i = 0; i < length; i++) {char now = string.charAt(i);int index = now - 'a';//index为字符now所处的位置if (nowNode.next[index] == null) {nowNode.next[index] = new TreeNode();}nowNode.isLeaf = false;// 先对当前node的path++,再转移到下一个nodenowNode.path++;nowNode = nowNode.next[index];}// 处理 ab abc ,通过前缀a查询,也需要查询出ab的情况nowNode.isWordEnd = true;//在最后的node,path和end++nowNode.path++;nowNode.end++;}/*** 返回这个前缀树总共插入了多少个字符串** @return*/public int size() {return root.path;}/*** 前缀树查询总共插入这个字符串多少次,如果没插入过,则返回0** @param string* @return*/public int getStringNum(String string) {if (string == null || string.length() == 0) {return 0;}int length = string.length();TreeNode nowNode = root;for (int i = 0; i < length; i++) {char now = string.charAt(i);int index = now - 'a';//如果没有这个节点,说明不存在,直接返回0if (nowNode.next[index] == null) {return 0;}nowNode = nowNode.next[index];}//此时nowNode已经处于最后一个节点return nowNode.end;}/*** 前缀树查询以这个字符串为前缀的字符串总共多少个(包括以他为结尾的)** @param string 前缀* @return*/public int getPrefixNum(String string) {if (string == null || string.length() == 0) {return 0;}int length = string.length();TreeNode nowNode = root;for (int i = 0; i < length; i++) {char now = string.charAt(i);int index = now - 'a';//如果没有这个节点,说明前缀不存在,直接返回0if (nowNode.next[index] == null) {return 0;}nowNode = nowNode.next[index];}//此时nowNode已经处于前缀的最后一个节点return nowNode.path;}//    public List<String> findByPrefix(String prefix) {public Set<String> findByPrefix(String prefix) {// 注意:根节点不存储任何元素TreeNode curNode = root;int prefixLen = prefix.length();// 1:找到prefix对应的TreeNode对象for (int i = 0; i < prefixLen; i++) {int idx = prefix.charAt(i) - 'a';TreeNode[] dataArr = curNode.next;if (dataArr[idx] == null) {System.out.println("not find!");return null;}// 非前缀的最后一个元素,遇到空,则说明要匹配的前缀不存在/*if (dataArr[idx] != null) {if (i > prefixLen - 1) {return null;} else {curNode = dataArr[idx];}}*/// 继续向下curNode = dataArr[idx];}// 2:根据prefix对应的TreeNode对象,递归找到所有的可能字符串TreeNode[] possibleTreeNodeArr = curNode.next;// 3:递归找到所有的可能字符串
//        List<String> possibleStrList = new ArrayList<>();Set<String> possibleStrList = new HashSet<>();/*for (int i = 0; i < possibleTreeNodeArr.length; i++) {if (possibleTreeNodeArr[i] != null) possibleStrList.add(prefix + (char) (i + 'a'));}for (int i = 0; i < possibleTreeNodeArr.length; i++) {queryAllPossibleStr(i, possibleTreeNodeArr, possibleStrList, prefix);}*/queryAllPossibleStr(0, curNode, possibleTreeNodeArr, possibleStrList, prefix);return possibleStrList;}private void queryAllPossibleStr(int i, TreeNode curNode, TreeNode[] possibleTreeNodeArr, Set<String> possibleStrList, String prefix) {if (i >= possibleTreeNodeArr.length || possibleTreeNodeArr == null) return;String newPrefix = prefix + (char) (i + 'a');// 元素为null,说明到达叶子节点if ((possibleTreeNodeArr[i] == null && curNode.isLeaf) || curNode.isWordEnd) {
//        if (possibleTreeNodeArr[i] == null && i == possibleTreeNodeArr.length - 1) {
//        if (possibleTreeNodeArr[i] != null && possibleTreeNodeArr[i].isLeaf) {possibleStrList.add(prefix);// 下层
//            queryAllPossibleStr(0, possibleTreeNodeArr[i], possibleTreeNodeArr[i].next, possibleStrList, newPrefix);} /*else {// 当前无元素,则向右继续找,有则向下和向右找if (possibleTreeNodeArr[i] != null) {// 下层queryAllPossibleStr(0, possibleTreeNodeArr[i], possibleTreeNodeArr[i].next, possibleStrList, newPrefix);}}*/// 当前无元素,则向右继续找,有则向下和向右找if (possibleTreeNodeArr[i] != null) {// 下层queryAllPossibleStr(0, possibleTreeNodeArr[i], possibleTreeNodeArr[i].next, possibleStrList, newPrefix);}// 不管咋的,都得向右→queryAllPossibleStr(i + 1, curNode, possibleTreeNodeArr, possibleStrList, prefix);}
}

重点关注两个方法,insertString插入方法,findByPrefix根据前缀获取匹配前缀的字符串列表方法。

测试代码:

public class Main {public static void main(String[] args) {TrieTree tree=new TrieTree();tree.insertString("aba");tree.insertString("abc");tree.insertString("abcd");tree.insertString("jack");tree.insertString("amazing");tree.insertString("express");tree.insertString("engine");tree.insertString("engines");tree.insertString("equipment");tree.insertString("j");
//		tree.insertString("aa");
//		tree.insertString("aa");
//		tree.insertString("ab");
//		tree.insertString("ba");
//		tree.insertString("jack");
//		tree.insertString("jaabcdef");//System.out.println(tree.root);//System.out.println(tree.size());//System.out.println(tree.getStringNum("aa"));//System.out.println(tree.getStringNum("ab"));//System.out.println(tree.getStringNum("ac"));System.out.println(tree.getPrefixNum("a"));System.out.println(tree.getPrefixNum("b"));System.out.println(tree.getPrefixNum("c"));System.out.println(tree.findByPrefix("a"));System.out.println(tree.findByPrefix("en"));}}

运行:

[aba, amazing, abc, abcd]
[engine, engines]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

3:有啥用

比如你要开发一个自动提示补全的idea插件,就像这样:
在这里插入图片描述

或者有其他的功能需要用到类似的功能,都可以考虑使用前缀树。

写在后面

不管是什么技术,只有用到了实际的功能中才算是真正的有用,因此在实际工作中我们要往如何落地应用的方向多考虑。

参考文章列表

Trie树(字典树,前缀树,键树)分析详解 。

前缀树是什么 前缀树的使用场景 。

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

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

相关文章

C++基础编程100题-014 OpenJudge-1.3-12 计算球的体积

更多资源请关注纽扣编程微信公众号 http://noi.openjudge.cn/ch0103/12/ 描述 对于半径为r的球&#xff0c;其体积的计算公式为$ V4/3*πr^3 $&#xff0c;这里取π 3.14。 现给定r&#xff0c;求V。 输入 输入为一个不超过100的非负实数&#xff0c;即球半径&#xff0…

FL Studio 21 集成了音频剪辑和增益控制,你可以更快、更精确地控制音频电平,包括自动交叉淡入淡出及更多功能

以通过控制色调、饱和度、亮度、文本、仪表和步进序列器的颜色来改变你的DAW外观&#xff0c; DAW“情绪主题”控制&#xff0c;水果编曲将变得与众不同。 更快的音频编辑 FL Studio 21 集成了音频剪辑包络和增益控制&#xff0c;你可以更快、更精确地控制音频电平&#xff0c…

aws的alb,多个域名绑定多个网站实践

例如首次创建的alb负载均衡只有www.xxx.com 需要添加 负载 test2.xxx.com aws的Route 53产品解析到负载均衡 www.xxx.com 添加CNAME&#xff0c;到负载均衡的dns字段axx test2.xxx.com 添加CNAME&#xff0c;到负载均衡的dns字段axx 主要介绍目标组和规则 创建alb就不介…

Spring底层原理之FactoryBean Bean工厂 单例对象 多例对象

FactoryBean 在 Spring Framework 中&#xff0c;FactoryBean 是一个用于创建其他 Bean 实例的特殊工厂 Bean。它允许开发者自定义 Bean 的创建逻辑&#xff0c;从而更加灵活地管理和配置 Bean 的实例化过程。 FactoryBean 接口 FactoryBean 接口是 Spring 框架中的一个重要…

推送电子邮箱与其他营销手段如何有效结合?

推送电子邮箱的效果如何&#xff1f;怎么优化邮件推送的策略&#xff1f; 将推送电子邮箱与其他营销手段有效结合&#xff0c;可以显著提升营销效果和用户体验。AokSend将探讨如何将推送电子邮箱与社交媒体营销、内容营销、搜索引擎优化&#xff08;SEO&#xff09;等手段相结…

ArkTS自定义组件

一、自定义组件基本结构 // 定义自定义组件 ButtonCom.ets Component export struct BtnCom{State msg: string "按钮";build() {Row(){Text(this.msg).onClick(() > {this.msg "测试"})}} } // 引入自定义组件 import {BtnCom} from "./Butto…

动物常见图像的图像分类数据集

常见动物图像分类数据集 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1zZnCUZuNlX6MjuZImlDjTw?pwd03b9 提取码&#xff1a;03b9 数据集信息介绍&#xff1a; 文件夹 大象 中的图片数量: 1446 文件夹 松鼠 中的图片数量: 1862 文件夹 河马 中的图片数量:…

理解和处理不同类型的上下文的新型的语言模型ReALM

在人类的日常交流中&#xff0c;模糊的代词如“他们”或“那个”常常出现&#xff0c;它们的意义通常依赖于上下文才能明确。这种上下文的理解对于对话助手来说至关重要&#xff0c;因为它们旨在提供一种自然的交流体验。然而&#xff0c;现有的对话助手在处理这类模糊引用时往…

SAP查看接口日志

大部分SAP项目都会与其他外围系统进行集成&#xff0c;那么日常工作中&#xff0c;如果遇到接口传输问题&#xff0c;我们应该怎样查询呢&#xff1f; 也许这篇文章可以稍微解答一些基础的内容。 1、事务代码SXI_MONITOR检查接口日志&#xff1a; 执行的起止日期默认带出&…

oracle11g rac静默dbca创建实例反复报监听问题

问题 处理方法 手动创建监听程序 重新静默dbca建库

Hume AI 将无比强大的情感AI聊天机器人带到了iPhone平台免费使用

总部位于美国的Hume AI研究实验室&#xff0c;致力于开发“针对人类福祉进行优化”的系统&#xff0c;最近推出了其EVI平台的iOS应用版本——名为Hume&#xff1a;您的个人人工智能。 这款新产品包括名为Kora的新型AI语音&#xff0c;以及对热门新Claude 3.5 Sonnet LLM的支持…

2024.6.25力扣刷题记录-周赛403

目录 一、3194. 最小元素和最大元素的最小平均值 二、3195. 包含所有 1 的最小矩形面积 I 三、3196. 最大化子数组的总成本 四、3197. 包含所有 1 的最小矩形面积 II 博主在比赛时只过了前两题。剩下跟着灵神做&#xff0c;来自视频&#xff1a; 【状态机 DP【力扣周赛 403…

Deepl网页版使用方法

Deepl网页版科学使用方法 Deepl 网页版使用方法重要链接使用方式一、在没有使用插件前的Deepl界面二、安装DeepL.Crack.v1.2.7插件三、插件安装后打开[Deepl官网](https://www.deepl.com/zh/translator)即可四、在翻译整篇文章的权限怎么设置 Deepl 网页版使用方法 重要链接 …

rider出现PluginException导致无法启动

目录 项目场景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; 项目场景&#xff1a; 由于deepinshot不适用于wayland&#xff0c;ubuntu自带的截图又难用的很&#xff0c;所以换了一款截图软件&#xff0c;叫flameshot&#xff0c;可支持在wayland上使…

基于SaaS平台的iHRM管理系统测试学习

目录 目录 1、登录模块 2、员工管理模块 3、Postmannewman软件的安装&#xff0c;学习 1、Postman的使用 2、Postman断言 3、全局变量和环境变量 4、请求时间戳 5、Postman关联 6、批量执行测试用例 7、Postman生成测试报告 8、Postman读取外部数据文件&#xff08…

昇思25天学习打卡营第01天|基本介绍

作为曾经的javaer&#xff0c;本着不断学习的初心&#xff0c;报名了昇思25天的课程&#xff0c;希望自己能学会点东西的目的。 昇思MindSpore介绍 昇思MindSpore是一个全场景深度学习框架&#xff0c;旨在实现易开发、高效执行、全场景统一部署三大目标。 其中&#xff0c;…

【干货】Jupyter Lab操作文档

Jupyter Lab操作文档1. 使用须知2. 定制化Jupyter设置主题显示代码行数设置语言更多设置 3. 认识Jupyter界面4. 初用Jupyter运行调试格式化查看源码 5. 使用Jupyter Terminal6. 使用Jupyter Markdown7. 上传下载文件&#xff08;云服务器中的Jupyter Lab&#xff09;上传文件到…

LeetCode 算法:二叉树的层序遍历 c++

原题链接&#x1f517;&#xff1a;二叉树的层序遍历 难度&#xff1a;中等⭐️⭐️ 题目 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;roo…

关于如何更好管理好数据库的一点思考

本文尝试从数据库设计理论、ER图简介、性能优化、避免过度设计及权限管理方面进行思考阐述。 一、数据库范式 以下通过详细的示例说明数据库范式的概念&#xff0c;将逐步规范化一个例子&#xff0c;逐级说明每个范式的要求和变换过程。 示例&#xff1a;学生课程登记系统 初始…

【Docker】容器

目录 1. 容器启动 2. 容器启动/重启/停止 3. 进入容器 4. 容器查询 5. docker 镜像的构建 方式一&#xff1a;docker 容器 commit 方式二&#xff1a;Dockerfile 定制镜像 1. 容器启动 docker run –it/-d –p/P –name imageID/name 2. 容器启动/重启/停止 docker sta…