当前位置: 首页 > news >正文

大一计算机的自学总结:前缀树(字典树、Trie树)

前言

前缀树,又称字典树,Trie树,是一种方便查找前缀信息的数据结构。

一、字典树的实现

1.类描述实现

#include <bits/stdc++.h>
using namespace std;class TrieNode
{
public:int pass=0;int end=0;TrieNode* nexts[26]={NULL};
};TrieNode* root=NULL;void insert(string word)
{TrieNode* node=root;node->pass++;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(node->nexts[path]==NULL){node->nexts[path]=new TrieNode();}node=node->nexts[path];node->pass++;}node->end++;
}int search(string word)
{TrieNode* node=root;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(node->nexts[path]==NULL){return 0;}node=node->nexts[path];}return node->end;
}int prefixNumber(string word)
{TrieNode* node=root;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(node->nexts[path]==NULL){return 0;}node=node->nexts[path];}return node->pass;
}void deleteWord(string word)
{if(search(word)>0){TrieNode* node=root;node->pass--;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(--node->nexts[path]->pass==0){node->nexts[path]=NULL;return ;}node=node->nexts[path];}node->end--;}
}int main()
{int m;cin>>m;root=new TrieNode();int op;string word;for(int i=0;i<m;i++){cin>>op;cin>>word;if(op==1){insert(word);}else if(op==2){deleteWord(word);}else if(op==3){cout<<(search(word)?"YES":"NO")<<endl;}else if(op==4){cout<<prefixNumber(word)<<endl;;}}
}

类描述的方法不推荐,重点是静态数组的实现方法。

2.静态数组实现

#include <bits/stdc++.h>
using namespace std;const int MAXN=150001;int trie[MAXN][26];
int treePass[MAXN]={0};
int treeEnd[MAXN]={0};
int cnt=1;//节点数void insert(string word)
{int cur=1;//节点编号treePass[cur]++;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(trie[cur][path]==0){trie[cur][path]=++cnt;}cur=trie[cur][path];treePass[cur]++;}treeEnd[cur]++;
}int search(string word)
{int cur=1;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(trie[cur][path]==0){return 0;}cur=trie[cur][path];}return treeEnd[cur];
}int prefixNumber(string word)
{int cur=1;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(trie[cur][path]==0){return 0;}cur=trie[cur][path];}return treePass[cur];
}void deleteWord(string word)
{if(search(word)>0){int cur=1;treePass[cur]--;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(--treePass[ trie[cur][path] ]==0){trie[cur][path]=0;return ;}cur=trie[cur][path];}treeEnd[cur]--;}
}int main()
{int m;cin>>m;int op;string word;for(int i=0;i<m;i++){cin>>op;cin>>word;if(op==1){insert(word);}else if(op==2){deleteWord(word);}else if(op==3){cout<<(search(word)?"YES":"NO")<<endl;}else if(op==4){cout<<prefixNumber(word)<<endl;;}}
}

首先说明前缀树的原理,每个节点有pass和end两个信息,同时还有指向下一个节点的指针,节点与节点间的路径表示每个字符。所以,在树往下扎到底的过程中,沿途路径经过的字符就组成了一个字符串,其中pass的数值表示的是有几个字符串经过这个节点,end表示的是有几个字符串在这个节点结束。如“aab”和“abc”,二者首先经过公共节点“a”,然后出现分支“a”和“b”,所以第一个“a”的pass=2,第二个“a”的pass=1,最后一个“b”的end=1,第二个“b”的pass=1,end=0。

首先,设置全局变量,trie数组的一维表示节点的编号,二维表示每条分支去往的下个节点的编号。因为字母只有26个,所以准备26大小即可。之后,设置cnt为节点个数,从1开始。

函数部分,首先是insert函数,用来插入字符串。首先,cur表示当前节点的编号,开始为1,然后先让pass++。之后遍历word字符串,每次取出当前字符作为path,若trie[cur][path]==0,即没有后续节点,那就让其等于++cnt,建出节点。之后让cur跳到下个节点,然后pass++。最后,让end++。

之后是search函数,用来搜索字符串个数。基本思路和insert差不多,只是若trie等于0,即没有后续节点,说明不存在这个字符串,就返回0;否则最后返回end,即字符串数量。

重点是prefixNumber函数,用来搜索以某个字符串为前缀的串。思路和search差不多,主要区别是最后返回的是pass,即前缀数量。

最后是delete函数,用来删除字符串。思路就是反向的insert,每次让pass-1。注意此时的判断,若下一个节点的pass-1后等于0,即后续节点被删没了,直接让trie等于0后结束即可。

二、前缀树相关题目

1.接头密匙

class Solution {
public:#define MAXN 100int trie[MAXN][12];int pass[MAXN]={0};int end[MAXN]={0};int cnt=1;void insert(string word){int cur=1;pass[cur]++;for(int i=0,path;i<word.length();i++){path=word[i]=='-'?10:(word[i]=='#'?11:word[i]-'0');if(trie[cur][path]==0){trie[cur][path]=++cnt;}cur=trie[cur][path];pass[cur]++;}end[cur]++;}int prefixNumber(string word){int cur=1;for(int i=0,path;i<word.length();i++){path=word[i]=='-'?10:(word[i]=='#'?11:word[i]-'0');if(trie[cur][path]==0){return 0;}cur=trie[cur][path];}return pass[cur];}vector<int> countConsistentKeys(vector<vector<int> >& b, vector<vector<int> >& a){for(int i=0;i<a.size();i++){string cur;for(int j=1;j<a[i].size();j++){cur+=a[i][j]-a[i][j-1]+'0';cur+='#';}insert(cur);}vector<int>ans(b.size(),0);for(int i=0;i<b.size();i++){string word;for(int j=1;j<b[i].size();j++){word+=b[i][j]-b[i][j-1]+'0';word+='#';}ans[i]=prefixNumber(word);}        return ans;}
};

 这个题的重点是你得看出来这个情境是求前缀。(捂脸)

之后转化一下,把数组里每个数的差变成字符串,两个数之间用“#”间隔,加上负号,trie一共开12大小即可。之后先把a数组insert进去,再根据b数组一个一个找就行。

2.数组中两个数的最大异或值

class Solution {
public:#define MAXN 3000001int trie[MAXN][2];int cnt=1;int high;void insert(int n){int cur=1;for(int i=high,status;i>=0;i--){status=1&(n>>i);if(trie[cur][status]==0){trie[cur][status]=++cnt;}cur=trie[cur][status];}}int maxXOR(int n){int ans=0;int cur=1;for(int i=high,status,want;i>=0;i--){status=1&(n>>i);want=status^1;if(trie[cur][want]==0){want^=1;}ans|=(status^want)<<i;cur=trie[cur][want];}return ans;}int findMaximumXOR(vector<int>& nums) {int mx=INT_MIN;for(int i=0;i<nums.size();i++){mx=max(mx,nums[i]);}//找最大值的前导1的位置for(int i=31;i>=0;i--){if( (mx&(1<<i) )!=0){high=i;break;}}for(int i=0;i<nums.size();i++){insert(nums[i]);}int ans=0;for(int i=0;i<nums.size();i++){ans=max(ans,maxXOR(nums[i]));}return ans;}
};

 这个题就需要一点思考了。首先思考要达成异或和最大,最好的办法肯定是选二进制中第一个1最靠前的数,即最大的数。之后,要想异或和最大,理想情况就是找每一位都与最大的数不同的数,这样异或起来每一位就都是1了。

所以,首先把最大值抓出来,接着为了加速,可以把最大值的前导1的数位取出来,这样后续从这个位置开始找即可,不需要从31位开始。再把每个数的二进制形式insert进去,由于只有0和1两种状态,所以trie的大小为2即可。

重点就是maxXOR函数,首先每次让status为n第i位上的状态。注意,这里由于trie只有0和1两条分支,所以选择让n>>i的方法。然后,最优选择肯定是找status不同的状态,所以want=status^1,若没有找到,就再^1回到原状态。最后把这一位的异或结果或进ans里即可。

这个题说实话还是有点难度的。

3.单词搜索 II

class Solution {
public:#define MAXN 10001int trie[MAXN][26];int pass[MAXN]={0};string end[MAXN];int cnt=1;void insert(string word){int cur=1;pass[cur]++;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(trie[cur][path]==0){trie[cur][path]=++cnt;}cur=trie[cur][path];pass[cur]++;}end[cur]=word;}int prefix(string word){int cur=1;for(int i=0,path;i<word.length();i++){path=word[i]-'a';if(trie[cur][path]==0){return 0;}cur=trie[cur][path];}return pass[cur];}//返回值为找到的字符串个数int dfs(vector<vector<char>>&board,int i,int j,int t,vector<string>&ans){if(i<0||i>=board.size()||j<0||j>=board[0].size()||board[i][j]==0){return 0;}int tmp=board[i][j];int path=tmp-'a';t=trie[t][path];if(pass[t]==0||t==0)//找过了或没有{return 0;}int num=0;if(end[t]!="")//找到一个{num++;ans.push_back(end[t]);end[t]="";}board[i][j]=0;num+=dfs(board,i+1,j,t,ans);num+=dfs(board,i-1,j,t,ans);num+=dfs(board,i,j+1,t,ans);num+=dfs(board,i,j-1,t,ans);pass[t]-=num;//找完一个就删除board[i][j]=tmp;return num;}vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {vector<string>ans;for(int i=0;i<words.size();i++){insert(words[i]);}for(int i=0;i<board.size();i++){for(int j=0;j<board[i].size();j++){dfs(board,i,j,1,ans);}}return ans;}
};

 这个题就更是群贤毕至,不仅有前缀树,还有带路径的递归和还原现场。

前缀树在这个题的作用就是剪枝,而且有三次剪枝。首先,通过前缀树,可以让每次递归去往的都是有效的格子。其次,这里让end数组直接存放整个字符串,可以方便递归结束时直接收集结果。最后,每次在找完一个字符串后就把节点删除,也可以减少不必要的搜索。

重点就是这个dfs函数,首先若越界了或走到走过的格子就返回0。之后取当前格子上的字符和path,t表示前缀树当前节点的编号,所以让t去下一个节点,若下一个节点找过了或者没有就返回0。然后若end有东西,说明找到了,就记录答案之后删除。接着分别去四个方向递归,注意这里在回来时不仅要把格子还原,还有让pass减去找到的数量,即删去找过的字符串,所以要让dfs函数带上返回值,表示找到的字符串个数。

有一说一这个题确实难。

总结

前缀树还是很强的,用好了能很大程度优化算法。

END

相关文章:

大一计算机的自学总结:前缀树(字典树、Trie树)

前言 前缀树&#xff0c;又称字典树&#xff0c;Trie树&#xff0c;是一种方便查找前缀信息的数据结构。 一、字典树的实现 1.类描述实现 #include <bits/stdc.h> using namespace std;class TrieNode { public:int pass0;int end0;TrieNode* nexts[26]{NULL}; };Tri…...

docker 安装的open-webui链接ollama出现网络错误

# 故事背景 部署完ollama以后&#xff0c;使用谷歌浏览器的插件Page Assist - 本地 AI 模型的 Web UI 可以比较流畅的使用DeepSeek&#xff0c;但是只局限于个人使用&#xff0c;想分享给更多的小伙伴使用&#xff0c;于是打算使用open-webui 来管理用户&#xff0c;经官网推荐…...

未来游戏:当人工智能重构虚拟世界的底层逻辑

未来游戏&#xff1a;当人工智能重构虚拟世界的底层逻辑 在《赛博朋克2077》夜之城的霓虹灯下&#xff0c;玩家或许已经注意到酒吧里NPC开始出现微表情变化&#xff1b;在《艾尔登法环》的开放世界中&#xff0c;敌人的战术包抄逐渐显露出类人智慧。这些细节预示着游戏产业正站…...

Redis集群主从切换源码解读

一切的开始 打开Redis5.0.5的源码中server.c&#xff0c;找到如下代码&#xff0c;这里运行了一个定时任务&#xff0c;每隔100毫秒执行一次。 /* Run the Redis Cluster cron. *//** 每隔100毫秒执行一次* 要求开启集群模式*/run_with_period(100) {if (server.cluster_enabl…...

javacv将mp4视频切分为m3u8视频并播放

学习链接 ffmpeg-demo 当前对应的 gitee代码 Spring boot视频播放(解决MP4大文件无法播放)&#xff0c;整合ffmpeg,用m3u8切片播放。 springboot 通过javaCV 实现mp4转m3u8 上传oss 如何保护会员或付费视频&#xff1f;优酷是怎么做的&#xff1f; - HLS 流媒体加密 ffmpe…...

Golang学习笔记_33——桥接模式

Golang学习笔记_30——建造者模式 Golang学习笔记_31——原型模式 Golang学习笔记_32——适配器模式 文章目录 桥接模式详解一、桥接模式核心概念1. 定义2. 解决的问题3. 核心角色4. 类图 二、桥接模式的特点三、适用场景1. 多维度变化2. 跨平台开发3. 动态切换实现 四、与其他…...

蜂鸟视图发布AI智能导购产品:用生成式AI重构空间服务新范式

在人工智能技术飞速发展的今天&#xff0c;北京蜂鸟视图正式宣布推出基于深度求索&#xff08;DeepSeek&#xff09;等大模型的《AI智能导购产品》&#xff0c;通过生成式AI与室内三维地图的深度融合&#xff0c;重新定义空间场景的智能服务体验。 这一创新产品将率先应用于购物…...

AI服务器散热黑科技:让芯片“冷静”提速

AI 服务器为何需要散热黑科技 在人工智能飞速发展的当下&#xff0c;AI 服务器作为核心支撑&#xff0c;作用重大。从互联网智能推荐&#xff0c;到医疗疾病诊断辅助&#xff0c;从金融风险预测&#xff0c;到教育个性化学习&#xff0c;AI 服务器广泛应用&#xff0c;为各类复…...

数据结构-栈、队列、哈希表

1栈 1.栈的概念 1.1栈:在表尾插入和删除操作受限的线性表 1.2栈逻辑结构: 线性结构(一对一) 1.3栈的存储结构:顺序存储(顺序栈)、链表存储(链栈) 1.4栈的特点: 先进后出(fisrt in last out FILO表)&#xff0c;后进先出 //创建栈 Stacklist create_stack() {Stacklist lis…...

安装海康威视相机SDK后,catkin_make其他项目时,出现“libusb_set_option”错误的解决方法

硬件&#xff1a;雷神MIX G139H047LD 工控机 系统&#xff1a;ubuntu20.04 之前运行某项目时&#xff0c;处于正常状态。后来由于要使用海康威视工业相机&#xff08;型号&#xff1a;MV-CA013-21UC&#xff09;&#xff0c;便下载了并安装了该相机的SDK&#xff0c;之后运行…...

【鸿蒙】ArkUI-X跨平台问题集锦

系列文章目录 【鸿蒙】ArkUI-X跨平台问题集锦 文章目录 系列文章目录前言问题集锦1、HSP,HAR模块中 无法引入import bridge from arkui-x.bridge;2、CustomDialog 自定义弹窗中的点击事件在Android 中无任何响应&#xff1b;3、调用 buildRouterMode() 路由跳转页面前&#xf…...

大模型驱动的业务自动化

大模型输出token的速度太低且为统计输出&#xff0c;所以目前大模型主要应用在toP&#xff08;人&#xff09;的相关领域&#xff1b;但其智能方面的优势又是如此的强大&#xff0c;自然就需要尝试如何将其应用到更加广泛的toM&#xff08;物理系统、生产系统&#xff09;领域中…...

ocr智能票据识别系统|自动化票据识别集成方案

在企业日常运营中&#xff0c;对大量票据实现数字化管理是一项耗时且容易出错的任务。随着技术的进步&#xff0c;OCR&#xff08;光学字符识别&#xff09;智能票据识别系统的出现为企业提供了一个高效、准确的解决方案&#xff0c;不仅简化了财务流程&#xff0c;还大幅提升了…...

[数据结构]红黑树,详细图解插入

目录 一、红黑树的概念 二、红黑树的性质 三、红黑树节点的定义 四、红黑树的插入&#xff08;步骤&#xff09; 1.为什么新插入的节点必须给红色&#xff1f; 2、插入红色节点后&#xff0c;判定红黑树性质是否被破坏 五、插入出现连续红节点情况分析图解&#xff08;看…...

【机器学习】超参数调优指南:交叉验证,网格搜索,混淆矩阵——基于鸢尾花与数字识别案例的深度解析

一、前言&#xff1a;为何要学交叉验证与网格搜索&#xff1f; 大家好&#xff01;在机器学习的道路上&#xff0c;我们经常面临一个难题&#xff1a;模型调参。比如在 KNN 算法中&#xff0c;选择多少个邻居&#xff08;n_neighbors&#xff09;直接影响预测效果。 • 蛮力猜…...

Burp Suite基本使用(web安全)

工具介绍 在网络安全的领域&#xff0c;你是否听说过抓包&#xff0c;挖掘漏洞等一系列的词汇&#xff0c;这篇文章将带你了解漏洞挖掘的热门工具——Burp Suite的使用。 Burp Suite是一款由PortSwigger Web Security公司开发的集成化Web应用安全检测工具&#xff0c;它主要用于…...

React实现自定义图表(线状+柱状)

要使用 React 绘制一个结合线状图和柱状图的图表&#xff0c;你可以使用 react-chartjs-2 库&#xff0c;它是基于 Chart.js 的 React 封装。以下是一个示例代码&#xff0c;展示如何实现这个需求&#xff1a; 1. 安装依赖 首先&#xff0c;你需要安装 react-chartjs-2 和 ch…...

从低清到4K的魔法:FlashVideo突破高分辨率视频生成计算瓶颈(港大港中文字节)

论文链接&#xff1a;https://arxiv.org/pdf/2502.05179 项目链接&#xff1a;https://github.com/FoundationVision/FlashVideo 亮点直击 提出了 FlashVideo&#xff0c;一种将视频生成解耦为两个目标的方法&#xff1a;提示匹配度和视觉质量。通过在两个阶段分别调整模型规模…...

Qt的QTabWidget的使用

在PyQt5中&#xff0c;QTabWidget 是一个用于管理多个选项卡页面的容器控件。以下是其使用方法的详细说明和示例&#xff1a; 1. 基本用法 import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QWidget, QLabel, QVBoxLayoutclass MainWindow(QMa…...

Next.js【详解】获取数据(访问接口)

Next.js 中分为 服务端组件 和 客户端组件&#xff0c;内置的获取数据各不相同 服务端组件 方式1 – 使用 fetch export default async function Page() {const data await fetch(https://api.vercel.app/blog)const posts await data.json()return (<ul>{posts.map((…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...

深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙

WebGL&#xff1a;在浏览器中解锁3D世界的魔法钥匙 引言&#xff1a;网页的边界正在消失 在数字化浪潮的推动下&#xff0c;网页早已不再是静态信息的展示窗口。如今&#xff0c;我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室&#xff0c;甚至沉浸式的V…...

李沐--动手学深度学习--GRU

1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …...