常用数据结构 - 前缀树

news2024/12/31 21:40:26

应用场景

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

数据结构

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

  • 根据字符串中包含的字符种类数 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 方法,在这个简单场景下表现不尽如人意。

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

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

相关文章

【Java数据结构】LinkedList与链表

认识LinkedList LinkedList就是一个链表&#xff0c;它也是实现List接口的一个类。LinkedList就是通过next引用将所有的结点链接起来&#xff0c;所以不需要数组。LinkedList也是以泛型的方法实现的&#xff0c;所以使用这个类都需要实例化对象。 链表分为很多种&#xff0c;比…

基于 Ragflow 搭建知识库-初步实践

基于 Ragflow 搭建知识库-初步实践 一、简介 Ragflow 是一个强大的工具&#xff0c;可用于构建知识库&#xff0c;实现高效的知识检索和查询功能。本文介绍如何利用 Ragflow 搭建知识库&#xff0c;包括环境准备、安装步骤、配置过程以及基本使用方法。 二、环境准备 硬件要…

【我的 PWN 学习手札】IO_FILE 之 stdout任意地址读

上一篇文章学会了stdin任意地址写【我的 PWN 学习手札】IO_FILE 之 stdin任意地址写-CSDN博客 本篇关注stdout利用手法&#xff0c;和上篇提及的手法有着异曲同工之妙 文章目录 前言 一、_IO_2_1_stdout_输出链&#xff0c;及利用思路 &#xff08;一&#xff09;_IO_2_1_std…

一网多平面

“一网多平面”是一种网络架构概念&#xff0c;具体指的是在一张物理网络之上&#xff0c;逻辑划分出“1N”个平面。以下是对“一网多平面”的详细解释&#xff1a; 定义与构成 01一网多平面 指的是在统一的物理网络基础设施上&#xff0c;通过逻辑划分形成多个独立的网络平面…

设计模式之状态模式:自动售货机的喜怒哀乐

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 一、状态模式概述 \quad 在我们的日常生活中&#xff0c;很多事物都具有不同的状态。比如我们经常使用的自动售货机&#xff0c;它就具有多种状态…

信息系统管理工程第8章思维导图

软考信管第8章的思维导图也实在是太长了&#xff0c;制作的耗时远超过之前的预计。给你看看思维导图的全貌如下&#xff0c;看看你能够在手机上滚动多少个屏幕 当你看到这段文字的时候&#xff0c;证明你把思维导图从上到下看完了&#xff0c;的确很长吧&#xff0c;第8章的教程…

Excel无法插入新单元格怎么办?有解决方法吗?

在使用Excel时&#xff0c;有时会遇到无法插入新单元格的困扰。这可能是由于多种原因导致的&#xff0c;比如单元格被保护、冻结窗格、合并单元格等。本文将详细介绍3种可能的解决方案&#xff0c;帮助你顺利插入新单元格。 一、消冻结窗格 冻结窗格功能有助于在滚动工作表时保…

深度学习笔记(12)——深度学习概论

深度学习概论 深度学习关系&#xff1a; 为什么机器人有一部分不在人工智能里面&#xff1a;机器人技术是一个跨学科的领域&#xff0c;它结合了机械工程、电子工程、计算机科学以及人工智能&#xff08;AI&#xff09;等多个领域的知识。 并不是所有的机器人都依赖于人工智能…

HEIC 是什么图片格式?如何把 iPhone 中的 HEIC 转为 JPG?

在 iPhone 拍摄照片时&#xff0c;默认的图片格式为 HEIC。虽然 HEIC 格式具有高压缩比、高画质等优点&#xff0c;但在某些设备或软件上可能存在兼容性问题。因此&#xff0c;将 HEIC 格式转换为更为通用的 JPG 格式就显得很有必要。本教程将介绍如何使用简鹿格式工厂&#xf…

flask后端开发(11):User模型创建+注册页面模板渲染

目录 一、数据库创建和配置信息1.新建数据库2.数据库配置信息3.User表4.ORM迁移 二、注册页面模板渲染1.导入静态文件2.蓝图注册路由 一、数据库创建和配置信息 1.新建数据库 终端中 CREATE DATABASE zhiliaooa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2…

【Next.js】001-项目初始化

【Next.js】001-项目初始化 文章目录 【Next.js】001-项目初始化一、前言二、自动创建项目1、环境要求2、创建项目创建命令创建演示生成的项目目录如果你不使用 npx 命令 3、运行项目脚本说明在开发环境运行项目查看页面 4、示例代码说明创建项目查看示例项目创建项目命令创建过…

系统安全——可信计算

可信计算 可信计算的起源 上世纪八十年代&#xff0c;TCSEC标准将系统中所有安全机制的总和定义为可信计算基 &#xff08;Trusted Computing Base TCB) TCB的要求是&#xff1a; 独立的&#xff08;independent&#xff09; 具有抗篡改性 tempering proof 不可旁路(无法窃…

Python学生管理系统(MySQL)

上篇文章介绍的Python学生管理系统GUI有不少同学觉得不错来找博主要源码&#xff0c;也有同学提到老师要增加数据库管理数据的功能&#xff0c;本篇文章就来介绍下python操作数据库&#xff0c;同时也对上次分享的学生管理系统进行了改进了&#xff0c;增加了数据库&#xff0c…

【Sentinel】流控效果与热点参数限流

目录 1.流控效果 1.1.warm up 2.2.排队等待 1.3.总结 2.热点参数限流 2.1.全局参数限流 2.2.热点参数限流 2.3.案例 1.流控效果 在流控的高级选项中&#xff0c;还有一个流控效果选项&#xff1a; 流控效果是指请求达到流控阈值时应该采取的措施&#xff0c;包括三种&…

《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS技术理念

1.2 技术理念 在万物智联时代重要机遇期&#xff0c;HarmonyOS结合移动生态发展的趋势&#xff0c;提出了三大技术理念&#xff08;如下图3-1所示&#xff09;&#xff1a;一次开发&#xff0c;多端部署&#xff1b;可分可合&#xff0c;自由流转&#xff1b;统一生态&#xf…

计算机组成——Cache

目录 为什么引入高速缓存&#xff1f; 数据查找方案&#xff1a; 命中率与缺失率 Cache和主存的映射方式 1.全相联映射 经典考法 覆盖问题 访存 2.组相联映射 3.直接映射&#xff08;和组相联类似&#xff09; 覆盖问题 替换算法 1.随机算法&#xff08;RAND&…

OpenCV和PyQt的应用

1.创建一个 PyQt 应用程序&#xff0c;该应用程序能够&#xff1a; 使用 OpenCV 加载一张图像。在 PyQt 的窗口中显示这张图像。提供四个按钮&#xff08;QPushButton&#xff09;&#xff1a; 一个用于将图像转换为灰度图一个用于将图像恢复为原始彩色图一个用于将图像进行翻…

基于Spring Boot的宠物领养系统的设计与实现(代码+数据库+LW)

摘 要 如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一些新的技术&#xff0c;而新技术的产生往往是为了解决现有问题而产生的。针对于宠物领…

uniapp 判断多选、选中取消选中的逻辑处理

一、效果展示 二、代码 1.父组件: :id=“this.id” : 给子组件传递参数【id】 @callParentMethod=“takeIndexFun” :给子组件传递方法,这样可以在子组件直接调用父组件的方法 <view @click="$refs.member.open()"

百度热力图数据日期如何选择

目录 1、看日历2、看天气 根据研究内容定&#xff0c;一般如果研究城市活力的话&#xff0c;通常会写“非重大节假日&#xff0c;非重大活动&#xff0c;非极端天气等”。南方晴天不多&#xff0c;有小雨或者中雨都可认为没有影响&#xff0c;要不然在南方很难找到完全一周没有…