常用数据结构 - 前缀树

应用场景

一般涉及到需要前缀信息来分析问题或者解决问题的时候,就应该考虑一下前缀树。
前缀树是对字符串每个位置上的字符进行编码,记录的是字符本身和字符串的信息,非常适合用于前缀匹配。

数据结构

前缀树的每个节点,是根据字符串的内容记录的路径信息,它有两种实现:

  • 根据字符串中包含的字符种类数 N N N,在每个节点上创建对应的 N 叉树。这样一来,每个节点的后继就可以通过树来轻松找到。另外还需要一个布尔型的变量 e n d end end,来标记是否有字符串在这里结束(或者用整型来统计次数)。这样的做法,可以参见 Leetcode 208.实现 Trie (前缀树)。
  • 字符串的路径信息,可以通过使用二维数组 t r e e tree tree 搭配计数变量 c o u n t count count 来维护。其中第一个下标根据某个字符信息来索引相应的类别信息,第二个下标则表示下一个节点的数据。此外,定义 p a s s pass pass e n d end end 数组来描述经过这个节点(包含到这里为止的前缀)的字符串以及以这个节点为终止位置(包含整个字符串)的字符串类别。

具体实现

树记录信息

class TreeNode {
    public int pass; // pass 表示经过当前节点(包含这个字符)的字符串数量
    public int end; // end 表示以当前节点为止(包含整个字符串)的字符串数量
    public TreeNode[] path; // 数组形式的树

    public TreeNode() {
        pass = 0;
        end = 0;
        path = new TreeNode[26];
    }
}

class Trie {
    private final TreeNode root;

    public Trie() {
        root = new TreeNode();
    }

    // 插入新字符串
    public void insert(String word) {
        TreeNode node = root;
        // 更新包含当前节点的字符串数量
        node.pass++;
        for (int i = 0, cur; i < word.length(); i++) {
            // cur 表示当前节点的下标
            cur = word.charAt(i) - 'a';
            if (node.path[cur] == null) {
                node.path[cur] = new TreeNode();
            }
            // 移动工作指针
            node = node.path[cur];
            node.pass++;
        }
        // 更新以当前节点为止的字符串数量
        node.end++;
    }

    // 统计某个字符串出现的次数
    public int countWords(String word) {
        TreeNode node = root;
        for (int i = 0, cur; i < word.length(); i++) {
            cur = word.charAt(i) - 'a';
            // 到某个节点时发现没有往下走的路径,则返回 0
            if (node.path[cur] == null) {
                return 0;
            }
            // 移动工作指针
            node = node.path[cur];
        }
        // 返回以当前节点为止的字符串数量
        return node.end;
    }

    // 统计以某个字符串为前缀的字符串的数量
    public int countPrefixes(String prefix) {
        TreeNode node = root;
        for (int i = 0, cur; i < prefix.length(); i++) {
            cur = prefix.charAt(i) - 'a';
            // 到某个节点时发现没有往下走的路径,则返回 0
            if (node.path[cur] == null) {
                return 0;
            }
            // 移动工作指针
            node = node.path[cur];
        }
        // 返回包含当前节点的字符串数量
        return node.pass;
    }

    // 删除字符串
    public void delete(String word) {
        // 确保该字符串存在,提高代码的健壮性
        if (countWords(word) > 0) {
            TreeNode node = root;
            // 更新包含该节点的字符串数量
            node.pass--;
            for (int i = 0, cur; i < word.length(); i++) {
                cur = word.charAt(i) - 'a';
                // 一旦更新过后经过下个节点的字符串数量为零,那么将走向它的路径制空
                if (--node.path[cur].pass == 0) {
                    node.path[cur] = null;
                    return;
                }
                // 移动工作指针
                node = node.path[cur];
            }
            // 更新以当前节点为止的字符串数量
            node.end--;
        }
    }
}

二维数组维护

import java.util.Arrays;

public class Trie {

    public static int MAX_N = 100;
    public static int[][] tree = new int[MAX_N][26]; // 二维数组维护路径信息
    public static int[] pass = new int[MAX_N]; // pass 表示经过某个节点(包含某个字符)的字符串数量
    public static int[] end = new int[MAX_N]; // end 表示以某个节点为止(包含某个字符串)的字符串数量
    public static int count; // count 维护存在的字符串种类数

    public Trie() {
        count = 1;
    }

    // 插入新字符串
    public static void insert(String word) {
        // 从类别为 1 的字符串开始访问
        int index = 1;
        // 更新包含该节点的字符串数量
        pass[index]++;
        for (int i = 0, cur; i < word.length(); i++) {
            cur = word.charAt(i) - 'a';
            // 如果这种字符串先前不存在,那么 count 先自增
            if (tree[index][cur] == 0) {
                tree[index][cur] = ++count;
            }
            // 移动工作指针
            index = tree[index][cur];
            pass[index]++;
        }
        // 更新以当前节点为止的字符串数量
        end[index]++;
    }

    // 统计某个字符串出现的次数
    public static int countWords(String word) {
        // 从类别为 1 的字符串开始访问
        int index = 1;
        for (int i = 0, cur; i < word.length(); i++) {
            cur = word.charAt(i) - 'a';
            // 如果这种字符串先前不存在,则返回 0
            if (tree[index][cur] == 0) {
                return 0;
            }
            // 移动工作指针
            index = tree[index][cur];
        }
        // 返回以当前节点为止的字符串数量
        return end[index];
    }

    // 统计以某个字符串为前缀的字符串的数量
    public static int countPrefixes(String pre) {
        // 从类别为 1 的字符串开始访问
        int index = 1;
        for (int i = 0, cur; i < pre.length(); i++) {
            cur = pre.charAt(i) - 'a';
            // 到某个节点时发现没有往下走的路径,则返回 0
            if (tree[index][cur] == 0) {
                return 0;
            }
            // 移动工作指针
            index = tree[index][cur];
        }
        // 返回包含当前节点的字符串数量
        return pass[index];
    }

    // 删除字符串
    public static void delete(String word) {
        // 确保该字符串存在,提高代码的健壮性
        if (countWords(word) > 0) {
            // 从类别为 1 的字符串开始访问
            int index = 1;
            for (int i = 0, cur; i < word.length(); i++) {
                cur = word.charAt(i) - 'a';
                // 一旦更新过后经过下个节点的字符串数量为零,那么将走向它的路径制空
                if (--pass[tree[index][cur]] == 0) {
                    tree[index][cur] = 0;
                    return;
                }
                // 移动工作指针
                index = tree[index][cur];
            }
            // 更新以当前节点为止的字符串数量
            end[index]--;
        }
    }

    // 由于定义的是静态变量,在不同测试用例之间需要清理空间防止出现脏数据
    // 清空二维数组
    public static void clear() {
        for (int i = 1; i <= count; i++) {
            Arrays.fill(tree[i], 0);
            end[i] = 0;
            pass[i] = 0;
        }
    }
}

总结梳理

实际上类定义的前缀树是更常用的选择,用数组来实现看起来很美好,其实需要一些时间来开大数组,更适合竞赛或者手动处理输入的场景。

后记

用 Leetcode 208.实现 Trie (前缀树) 进行测试,类实现能很好地完成目标;而二维数组的实现需要将数组的第一个维度增加到 35000 35000 35000 以上,并且在对象初始化时需要先调用 clear 方法,在这个简单场景下表现不尽如人意。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面试241228

面试可参考 1、cas的概念 2、AQS的概念 3、redis的数据结构 使用场景 不熟 4、redis list 扩容流程 5、dubbo 怎么进行服务注册和调用&#xff0c;6、dubbo 预热 7如何解决cos上传的安全问题kafka的高并发高吞吐的原因ES倒排索引的原理 spring的 bean的 二级缓存和三级缓存 spr…

2024 年发布的 Android AI 手机都有什么功能?

大家好&#xff0c;我是拭心。 2024 年是 AI 快速发展的一年&#xff0c;这一年 AI 再获诺贝尔奖&#xff0c;微软/苹果/谷歌等巨头纷纷拥抱 AI&#xff0c;多款强大的 AI 手机进入我们的生活。 今年全球 16% 的智能手机出货量为 AI 手机&#xff0c;到 2028 年&#xff0c;这…

SimForge HSF 案例分享|复杂仿真应用定制——UAVSim无人机仿真APP(技术篇)

导读 「神工坊」核心技术——「SimForge HSF高性能数值模拟引擎」支持工程计算应用的快速开发、自动并行&#xff0c;以及多域耦合、AI求解加速&#xff0c;目前已实现航发整机数值模拟等多个系统级高保真数值模拟应用落地&#xff0c;支持10亿阶、100w核心量级的高效求解。其低…

Windows 下安装 triton 教程

目录 背景解决方法方法一&#xff1a;&#xff08;治标不治本&#xff09;方法二&#xff1a;&#xff08;triton-windows&#xff09;- 安装 MSVC 和 Windows SDK- vcredist 安装- whl 安装- 验证 背景 triton 目前官方只有Linux 版本&#xff0c;若未安装&#xff0c;则会出…

Kali 自动化换源脚本编写与使用

1. 背景与需求 在使用 Kali Linux 的过程中&#xff0c;软件源的配置对系统的更新与软件安装速度至关重要。 Kali 的默认官方源提供了安全且最新的软件包&#xff0c;但有时由于网络条件或地理位置的限制&#xff0c;使用官方源可能会出现速度较慢的问题。 为了解决这一问题&a…

Ajax数据爬取

有时我们用requests 抓取页面得到的结果&#xff0c;可能和在浏览器中看到的不一样:在浏览器中可以看到正常显示的页面数据&#xff0c;而使用requests 得到的结果中并没有这些数据。这是因为 requests 获取的都是原始 HTML 文档&#xff0c;而浏览器中的页面是JavaScript 处理…

基于Docker的ETCD分布式集群

目录 1. 说明 2. 配置表 3. 步骤 3.1 放行端口 3.2 docker-compose 文件 3.3 部署到3台服务器 3.4 相关命令 4. 参考 1. 说明 - 以docker容器方式实现ETCD分布式集群&#xff0c;为其他项目提供支持&#xff0c;经过反复试验成功部署(网上资料大都过期或部署失败)。 -…

CUDA与Microsoft Visual Studio不兼容问题

简介&#xff1a;在安装一些 python库时&#xff0c;涉及到第三方库&#xff08;特别是需要引用 C 代码&#xff09;时&#xff0c;通常的安装方式会涉及到编译过程&#xff0c;通常称为"源代码安装"&#xff08;source installation&#xff09;&#xff0c;或是 “…

Trimble天宝X9三维扫描仪为建筑外墙检测提供了全新的解决方案【沪敖3D】

随着城市化进程的快速推进&#xff0c;城市高层建筑不断增多&#xff0c;对建筑质量的要求也在不断提高。建筑外墙检测&#xff0c;如平整度和垂直度检测&#xff0c;是衡量建筑质量的重要指标之一。传统人工检测方法不仅操作繁琐、效率低下&#xff0c;还难以全面反映墙体的真…

python爬虫——爬取全年天气数据并做可视化分析

一、主题页面的结构与特征分析 1&#xff0e;主题页面的结构与特征分析 目标内容界面&#xff1a; 2. Htmls 页面解析 3&#xff0e;节点查找方法与遍历方法 查找方法&#xff1a;find(): 查找第一个匹配到的节点。find_all(): 查找所有匹配到的节点&#xff0c;并返回一个…

MATLAB程序转C# WPF,dll集成,混合编程

工作中遇到一个需求&#xff0c;有一部分算法的代码需要MATLAB来进行处理&#xff0c;而最后需要集成到C#中的wpf项目中去&#xff0c;选择灵活性更高的dll&#xff0c;去进行集成。&#xff08;可以简单理解为&#xff1a;将MATLAB的函数&#xff0c;变为C#中类的函数成员&…

Ubuntu24.04最新版本安装详细教程

Ubuntu 24.04 LTS发布说明 推荐的系统配置要求&#xff1a; 双核2 GHz处理器或更高 4 GB系统内存 25 GB磁盘存储空间 可访问的互联网 光驱或USB安装介质 Ubuntu 24.04官方下载网址&#xff1a;https://cn.ubuntu.com/download/desktop 04. Ubuntu 22.04(创建虚拟机方式一) 4…

【YOLO算法改进】ALSS-YOLO:无人机热红外图像|野生动物小目标检测

目录 论文信息 论文创新点 1.自适应轻量通道分割和洗牌&#xff08;ALSS&#xff09;模块 2.轻量坐标注意力&#xff08;LCA&#xff09;模块 3.单通道聚焦模块 4.FineSIOU损失函数 摘要 架构设计 轻量高效网络架构 - ALSS模块 LCA模块 单通道聚焦模块 损失函数优…

【PDF物流单据提取明细】批量PDF提取多个区域内容导出表格或用区域内容对文件改名,批量提取PDF物流单据单号及明细导出表格并改名的技术难点及小节

相关阅读及下载&#xff1a; PDF电子物流单据&#xff1a; 批量PDF提取多个区域局部内容重命名PDF或者将PDF多个局部内容导出表格&#xff0c;具体使用步骤教程和实际应用场景的说明演示https://mp.weixin.qq.com/s/uCvqHAzKglfr40YPO_SyNg?token720634989&langzh_CN扫描…

MySQL数据库笔记——主从复制

大家好&#xff0c;这里是Good Note&#xff0c;关注 公主号&#xff1a;Goodnote&#xff0c;本文详细介绍 MySQL的主从复制&#xff0c;从原理到配置再到同步过程。 文章目录 简介核心组件主从复制的原理作用主从复制的线程模型主从复制的模式形式复制的方式设计复制机制主从…

大数据技术-Hadoop(三)Mapreduce的介绍与使用

目录 一、概念和定义 二、WordCount案例 1、WordCountMapper 2、WordCountReducer 3、WordCountDriver 三、序列化 1、为什么序列化 2、为什么不用Java的序列化 3、Hadoop序列化特点&#xff1a; 4、自定义bean对象实现序列化接口&#xff08;Writable&#xff09; 4…

从零开始学TiDB(7)TiDB 的MPP架构概述

MPP架构介绍&#xff1a; 如图&#xff0c;TiDB Server 作为协调者&#xff0c;首先 TiDB Server 会把每个TiFlash 拥有的region 会在TiFlash上做交换&#xff0c;让表连接在一个TiFlash上。另外 TiFlash会作为计算节点&#xff0c;每个TiFlash都负责数据交换&#xff0c;表连接…

接雨水-力扣热题100

题目&#xff1a; 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1]输出&#xff1a;6解释&#xff1a;上面是由数组 [0,1,0,2,1,…

AI大模型语音识别转文字

提取音频 本项目作用在于将常见的会议录音文件、各种语种音频文件进行转录成相应的文字&#xff0c;也可从特定视频中提取对应音频进行转录成文字保存在本地。最原始的从所给网址下载对应视频和音频进行处理。下载ffmpeg(https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-…

基于微信小程序的校园点餐平台的设计与实现(源码+SQL+LW+部署讲解)

文章目录 摘 要1. 第1章 选题背景及研究意义1.1 选题背景1.2 研究意义1.3 论文结构安排 2. 第2章 相关开发技术2.1 前端技术2.2 后端技术2.3 数据库技术 3. 第3章 可行性及需求分析3.1 可行性分析3.2 系统需求分析 4. 第4章 系统概要设计4.1 系统功能模块设计4.2 数据库设计 5.…