数据结构之前缀树

写在前面

源码 。

前缀树,又叫做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,一经查实,立即删除!

相关文章

典型传感器简介及驱动安装

双目视觉传感器 Indemind 传感器简介 INDEMIND M1 是专为开发者提供的一款硬件&#xff0c;采用“双目摄像头IMU”多传感器融合架构与 微秒级时间同步机制&#xff0c;为视觉 SLAM 研究提供精准稳定数据源&#xff0c;以满足 SLAM 研究、导航及 避障开发、视觉动作捕捉开发、…

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…

C# 高频面试题

C# 初级面试题及详细解答 1. C#中的值类型和引用类型的区别是什么&#xff1f; 解答: 值类型存储在堆栈上&#xff0c;直接包含数据&#xff1b;引用类型存储在堆上&#xff0c;存储的是对象的引用。值类型包括基本类型如 int、float 和 struct&#xff1b;引用类型包括 clas…

Perl编程探索:深入理解数组变量

Perl语言以其在文本处理和系统管理任务中的强大能力而受到广泛欢迎。在Perl中&#xff0c;数组是处理多个值集合的一种关键数据结构。本文将深入探讨Perl中的数组变量&#xff0c;包括它们的声明、初始化、访问和操作。 Perl数组的基本概念 在Perl中&#xff0c;数组可以用来…

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

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

代码随想录算法训练营第十四天| 110.平衡二叉树 | 257. 二叉树的所有路径 | 404.左叶子之和 | 222.完全二叉树的节点个数

110.平衡二叉树 &#xff08;优先掌握递归&#xff09; 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;后序遍历求高度&#xff0c;高度判断是否平衡 | LeetCode&#xff1a;110.平衡二叉树_哔哩哔哩_bilibili 1. 不知道咋做。 2. 在求二叉树的高度的代码上改&#xff…

学习面向对象前--Java基础练习题

前言 写给所有一起努力学习Java的朋友们&#xff0c;敲代码本身其实是我们梳理逻辑的一个过程。我们在学习Java代码的过程中&#xff0c;除了需要学习Java的一些基本操作及使用&#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就不介…

windows系统命令设置ipv4参数(手动、自动)、设置DNS解析、命令设置计算机主机名

命令设置ipv4网络参数。 命令提示符中使用netsh命令为网络接口&#xff08;网络连接&#xff09;配置ipv4参数。命令格式如下&#xff1a; netsh interface ip set address "接口名称" static ip地址 子网掩码 [默认网关] 案例&#xff1a; 将本地 "以太网…

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

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

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

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

ubuntu22.04安装AFLGo

git clone gitgithub.com:aflgo/aflgo.git cd aflgo export AFLGO$PWD sudo ./build.sh然后 上面的是官方流程 主要看下面 把build.sh里三个wget的先下好&#xff0c;放在对应路径&#xff0c;然后把他的注释掉&#xff0c;不然下载时间很长很长。然后下python那边容易报错&a…

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建库

【Linux】性能分析器 gperftools 详解

1、安装 1.1 源码安装 1)源码下载 最新版本:https://github.com/gperftools/gperftools 稳定版本:https://github.com/gperftools/gperftools/releases 2)编译 ./configure make -j83)安装,默认安装在/usr/local/lib目录下 sudo make install1.2 命令安装 以Ubuntu…

LearnOpenGL - Android OpenGL ES 3.0 YUV 渲染

系列文章目录 LearnOpenGL 笔记 - 入门 01 OpenGLLearnOpenGL 笔记 - 入门 02 创建窗口LearnOpenGL 笔记 - 入门 03 你好&#xff0c;窗口LearnOpenGL 笔记 - 入门 04 你好&#xff0c;三角形OpenGL - 如何理解 VAO 与 VBO 之间的关系LearnOpenGL - Android OpenGL ES 3.0 绘制…

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

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