大一计算机的自学总结:前缀树(字典树、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

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

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

相关文章

【存储中间件API】MySQL、Redis、MongoDB、ES常见api操作及性能比较

常见中间件api操作及性能比较 ☝️ MySQL crud操作✌️ maven依赖✌️ 配置✌️ 定义实体类✌️ 常用api ☝️ Redis crud操作✌️ maven依赖✌️ 配置✌️ 常用api ☝️ MongoDB crud操作✌️ maven依赖✌️ 配置文件✌️ 定义实体类✌️ MongoDB常用api ☝️ ES crud操作 ⭐️…

51单片机入门_10_数码管动态显示(数字的使用;简单动态显示;指定值的数码管动态显示)

接上篇的数码管静态显示&#xff0c;以下是接上篇介绍到的动态显示的原理。 动态显示的特点是将所有位数码管的段选线并联在一起&#xff0c;由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选&#xff…

C++入门《类和对象》之《运算符重载》详解|成员函数重载/非成员函数重载

C 中&#xff0c;运算符重载是一种特殊的函数&#xff0c;它允许程序员为自定义的数据类型&#xff08;如类和结构体&#xff09;重新定义运算符的行为&#xff0c;使得这些运算符能够像处理内置数据类型一样处理自定义类型的数据。下面将从多个方面详细讲解 C 里的运算符重载。…

Salesforce 检索Layout的设定

做了许多Object&#xff0c;却想不起来怎么设置我的Listview的项目了。 問題&#xff1a; salesforce 最近参照したオブジェクト 表示項目を変更したいですが、「検索レイアウト」の選択メニューが該当オブジェクトのオブジェクトマネージャーから出てないです。 解決方法&am…

SECS/GEM300应用案例参考

GEM300 是一种用于半导体制造领域的通信协议标准&#xff0c;主要用于支持 300mm 晶圆制造的自动化生产。以下是 GEM300 的一些具体应用案例&#xff1a; 1. 半导体设备集成 设备制造商的应用&#xff1a;广州金南瓜科技有限公司通过 GEM300 SDK&#xff0c;帮助国内多个半导体…

win10系统上的虚拟机安装麒麟V10系统提示找不到操作系统

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 win10系统上的虚拟机安装麒麟V10系统提示找不到操作系统&#xff0c;报错&#xff1a;Operating System not found 二、原因分析 国产系统&#xff0c;需要注意的点&#xff1a; 需要看你的系统类…

情书网源码 情书大全帝国cms7.5模板

源码介绍 帝国cms7.5仿《情书网》模板源码&#xff0c;同步生成带手机站带采集。适合改改做文学类的网站。 效果预览 源码获取 情书网源码 情书大全帝国cms7.5模板

C语言题目:链表数据求和操作

题目描述 读入10个复数&#xff0c;建立对应链表&#xff0c;然后求所有复数的和。 输入格式 无 输出格式 无 样例输入 1 2 1 3 4 5 2 3 3 1 2 1 4 2 2 2 3 3 1 1 样例输出 2323i 代码功能概述 createNode 函数&#xff1a; 创建一个包含 10 个复数节点的链表。 每个…

STM32 ADC介绍(硬件原理篇)

目录 背景 AD转换器 采样与保持 量化 编码 AD转换器转换原理 DA转换原理 AD转换原理 1.逐次逼近型AD转换器 2.并联比较型AD转换器 编码器 同步D触发器和边沿D触发器 基本RS触发器 同步RS触发器 同步D触发器 边沿型D触发器&#xff08;维持-阻塞D触发器&#xff…

公网远程家里局域网电脑过程详细记录,包含设置路由器。

由于从校内迁居小区,校内需要远程控制访问小区内个人电脑,于是早些时间刚好自己是电信宽带,可以申请公网ipv4不需要花钱,所以就打电话直接申请即可,申请成功后访问光猫设备管理界面192.168.1.1,输入用户名密码登录超管(密码是网上查下就有了)设置了光猫为桥接模式,然后…

流行编程语言全解析:优势、应用与短板

Python&#xff1a; 优势 Python 以其简洁、易读的语法闻名&#xff0c;新手能快速上手。丰富的库和框架&#xff0c;能极大地提高开发效率。 适用领域 数据科学与分析&#xff1a;处理和分析大规模数据集&#xff0c;进行数据可视化。典型示例&#xff1a;Google 用 Pytho…

统信服务器操作系统V20 1070A 安装docker新版本26.1.4

应用场景&#xff1a; 硬件/整机信息&#xff1a;x86平台、深信服超融合平台 OS版本信息&#xff1a;统信V20 1070a 1.获取docker二进制包 链接: https://pan.baidu.com/s/1SukBlra0mQxvslTfFakzGw?pwd5s5y 提取码: 5s5y tar xvf docker-26.1.4.tgz groupadd docker ch…

在 Vue 3 中使用 Lottie 动画:实现一个加载动画

在现代前端开发中&#xff0c;动画是提升用户体验的重要元素之一。Lottie 是一个流行的动画库&#xff0c;它允许我们使用 JSON 文件来渲染高质量的动画。本文将介绍如何在 Vue 3 项目中集成 Lottie 动画&#xff0c;并实现一个加载动画效果。 如果对你有帮助请帮忙点个&#x…

【Spring】Spring配置文件

目录 ​什么是配置文件&#xff1f; 配置文件的作用 SpringBoot配置文件 配置文件格式 配置文件的优先级 properties配置文件说明 properties基本语法 读取配置文件 properties缺点 yml配置文件说明 yml基本语法 使用yml连接数据库 yml配置不同数据类型及null 注意…

蓝桥杯篇---实时时钟 DS1302

文章目录 前言特点简介1.低功耗2.时钟/日历功能3.32字节的额外RAM4.串行接口 DS1302 引脚说明1.VCC12.VCC23.GND4.CE5.I/O6.SCLK DS1302 寄存器1.秒寄存器2.分钟寄存器3.小时寄存器4.日寄存器5.月寄存器6.星期寄存器7.年寄存器8.控制寄存器 DS1302 与 IAP25F2K61S2 的连接1.CE连…

Dubbo:高效的分布式服务框架

引言 在当今互联网应用的快速发展中&#xff0c;微服务架构已经成为一种主流的设计模式&#xff0c;它将一个大型单体应用拆分成多个小型、松耦合的服务。Dubbo 作为一款由阿里巴巴开源的 RPC 服务框架&#xff0c;专门为解决分布式系统中服务通信和治理的问题而设计。本文将深…

Visual Studio Code使用ai大模型编成

1、在Visual Studio Code搜索安装roo code 2、去https://openrouter.ai/settings/keys官网申请个免费的配置使用

【Javascript Day18】

目录 标签事件绑定的属性参数 阻止默认行为 dialog的实现及组织冒泡&#xff08;捕获&#xff09;传递 基于冒泡的事件委托 键盘事件的事件源对象信息 JS的自动触发操作 标签事件绑定的属性参数 <!-- 标签上的事件绑定&#xff0c;事件源对象通过 关键字event传递 --…

解锁机器学习核心算法 | 支持向量机:机器学习中的分类利刃

一、引言 在机器学习的庞大算法体系中&#xff0c;有十种算法被广泛认为是最具代表性和实用性的&#xff0c;它们犹如机器学习领域的 “十大神器”&#xff0c;各自发挥着独特的作用。这十大算法包括线性回归、逻辑回归、决策树、随机森林、K - 近邻算法、K - 平均算法、支持向…

玩客云 IP查找

1.玩客云使用静态IP在不同网段路由器下不能使用&#xff0c;动态不好找IP地址 1.1使用python3 实现自动获取发送 import requests import os import socket# 从环境变量获取 PushPlus 的 token 和群组编码 PUSH_PLUS_TOKEN os.getenv("PUSH_PLUS_TOKEN") PUSH_PLU…