数据结构与算法学习(一)

1 字典树(前缀树)

前缀树是N叉树的一种特殊形式。通常来说,一个前缀树是用来存储字符串的。前缀树的每一个节点代表一个字符串(前缀)。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的原始字符串,以及通往该子节点路径上所有的字符组成的。
在这里插入图片描述

对于前缀树,根节点不包含信息,路径保持字符,结点存两个值:pass 和 end。

1. Trie() 初始化前缀树对象
2. void insert(string word) 将字符串word插入前缀树之中
3. int search(string word) 返回前缀树中字符串word的实例个数
4. int prefixNumber(string prefix) 返回前缀树中以prefix为前缀的字符串个数
5. void delete(string word) 从前缀树中移出字符串word

(1) 从类的角度进行描述和实现(哈希)

数据结构的实现,采用struct来构建Trie的节点

// 字典树节点结构struct TrieNode {int pass;       // 有多少个单词经过此节点int end;        // 有多少个单词以此节点结尾std::unordered_map<char, TrieNode*> nexts; // 子节点映射表TrieNode() : pass(0), end(0) {}~TrieNode() {// 递归删除所有子节点,防止内存泄漏for (auto& pair : nexts) {delete pair.second;}}};TrieNode* root; // 根节点

将字符串word插入前缀树之中

void insert(const std::string& word) {if (word.empty()) return;TrieNode* node = root;node->pass++;for (char c : word) {if (node->nexts.find(c) == node->nexts.end()) {node->nexts[c] = new TrieNode();}node = node->nexts[c];node->pass++;}node->end++;
}

搜索字符串在树中出现的次数

int countWordsEqualTo(const std::string& word) const {const TrieNode* node = root;// 利用哈希表来寻找下一个节点for (char c : word) {auto it = node->nexts.find(c);if (it == node->nexts.end()) {return 0;}node = it->second;}return node->end;
}

返回前缀树中以prefix为前缀的字符串个数

int countWordsStartingWith(const std::string& prefix) const {const TrieNode* node = root;for (char c : prefix) {auto it = node->nexts.find(c);if (it == node->nexts.end()) {return 0;}node = it->second;}return node->pass;
}

从前缀树中删除指定单词

void erase(const std::string& word) {if (word.empty() || countWordsEqualTo(word) == 0) return;TrieNode* node = root;node->pass--; // root中的pass表示有多少个wordfor (char c : word) {TrieNode* next = node->nexts[c];if (--next->pass == 0) {// 递归删除子树delete next;node->nexts.erase(c);return;}node = next;}node->end--;
}

最终代码实现

#include <string>
#include <unordered_map>class Trie {
private:// 字典树节点结构struct TrieNode {int pass;       // 有多少个单词经过此节点int end;        // 有多少个单词以此节点结尾std::unordered_map<char, TrieNode*> nexts; // 子节点映射表TrieNode() : pass(0), end(0) {}~TrieNode() {// 递归删除所有子节点,防止内存泄漏for (auto& pair : nexts) {delete pair.second;}}};TrieNode* root; // 根节点public:// 构造函数Trie() : root(new TrieNode()) {}// 析构函数~Trie() {delete root;}// 插入单词到字典树void insert(const std::string& word) {if (word.empty()) return;TrieNode* node = root;node->pass++;for (char c : word) {if (node->nexts.find(c) == node->nexts.end()) {node->nexts[c] = new TrieNode();}node = node->nexts[c];node->pass++;}node->end++;}// 从字典树中删除单词void erase(const std::string& word) {if (word.empty() || countWordsEqualTo(word) == 0) return;TrieNode* node = root;node->pass--;for (char c : word) {TrieNode* next = node->nexts[c];if (--next->pass == 0) {// 递归删除子树delete next;node->nexts.erase(c);return;}node = next;}node->end--;}// 统计完全匹配的单词数量int countWordsEqualTo(const std::string& word) const {const TrieNode* node = root;for (char c : word) {auto it = node->nexts.find(c);if (it == node->nexts.end()) {return 0;}node = it->second;}return node->end;}// 统计以prefix为前缀的单词数量int countWordsStartingWith(const std::string& prefix) const {const TrieNode* node = root;for (char c : prefix) {auto it = node->nexts.find(c);if (it == node->nexts.end()) {return 0;}node = it->second;}return node->pass;}
};

2 从数组的角度进行实现

#include <string>
#include <cstring>class Trie {
private:static const int MAXN = 150001; // 最大节点数,可根据数据量调整int tree[MAXN][26];             // 存储子节点的索引int end[MAXN];                  // 标记该节点是否为单词结尾int pass[MAXN];                 // 记录经过该节点的单词数量int cnt;                        // 当前使用的节点数量public:// 初始化字典树Trie() {build();}// 重置字典树void build() {cnt = 1;memset(tree, 0, sizeof(tree));memset(end, 0, sizeof(end));memset(pass, 0, sizeof(pass));}// 插入单词到字典树void insert(const std::string& word) {if (word.empty()) return;int cur = 1; // 从根节点开始(索引1)pass[cur]++; // 根节点pass值增加for (char c : word) {int path = c - 'a'; // 计算字符对应的路径索引(0-25)if (tree[cur][path] == 0) {tree[cur][path] = ++cnt; // 分配新节点}cur = tree[cur][path];pass[cur]++; // 经过当前节点的单词数+1}end[cur]++; // 单词结尾标记+1}// 统计完全匹配的单词数量int search(const std::string& word) const {if (word.empty()) return 0;int cur = 1;for (char c : word) {int path = c - 'a';if (tree[cur][path] == 0) {return 0; // 路径不存在,单词不存在}cur = tree[cur][path];}return end[cur]; // 返回单词结尾标记值}// 统计以prefix为前缀的单词数量int prefixNumber(const std::string& prefix) const {if (prefix.empty()) return 0;int cur = 1;for (char c : prefix) {int path = c - 'a';if (tree[cur][path] == 0) {return 0; // 前缀路径不存在}cur = tree[cur][path];}return pass[cur]; // 返回经过该节点的单词数}// 从字典树中删除单词void deleteWord(const std::string& word) {if (word.empty() || search(word) == 0) return;int cur = 1;pass[cur]--; // 根节点pass值减1for (char c : word) {int path = c - 'a';int next = tree[cur][path];if (--pass[next] == 0) {tree[cur][path] = 0; // 移除路径return; // 子树已删除,直接返回}cur = next;}end[cur]--; // 单词结尾标记减1}// 清空字典树void clear() {for (int i = 1; i <= cnt; i++) {memset(tree[i], 0, sizeof(tree[i]));end[i] = 0;pass[i] = 0;}cnt = 1;}
};

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

  1. 指针类型的Trie解决
struct Trie{// 只包含0和1两个数,因此只需要两个左右指针Trie* left = nullptr;Trie* right = nullptr;Trie() {}
};class Solution {
private:Trie* root = new Trie();static constexpr int HIGH_BIT = 30;public:void insert(int num){Trie* cur = root;for(int k = HIGH_BIT; k >= 0; k--){int bit = (num >> k) & 1; //保存第k位的值if(bit == 0){if(!cur -> left){cur -> left = new Trie();}cur = cur -> left; //此时指向左边}else{if(!cur -> right){cur -> right = new Trie();}cur = cur -> right;}}}int MaxXor(int num){Trie* cur = root;int x = 0;for(int k = HIGH_BIT; k >= 0; k--){int bit = (num >> k) & 1;if(bit == 0){//此时希望异或最大,去寻找包含1的右节点if(cur -> right){cur = cur -> right;x = (x << 1) + 1;}else{cur = cur -> left;x = x << 1;}}else{if(cur -> left){cur = cur -> left;x = (x << 1) + 1;}else{cur = cur -> right;x = x << 1;}}}return x;}int findMaximumXOR(vector<int>& nums) {int x = 0;for(int i = 1; i < nums.size(); i++){insert(nums[i-1]);x = max(x, MaxXor(nums[i]));}return x;}
};
  1. 数组类型的Trie解决

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

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

相关文章

LeetCode Hot 100 二叉树的最大深度

给定一个二叉树 root &#xff0c;返回其最大深度。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。示例 1&#xff1a;输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a;输入&#xff1a;root [1,null,2] 输出&#…

Flutter基础(前端教程①①-底部导航栏)

1. 主页面&#xff08;BottomNavBarPage&#xff09;用 _currentBarIndex 记录当前选中的导航索引&#xff08;默认 0&#xff0c;即首页&#xff09;。用 IndexedStack 管理 4 个页面&#xff0c;通过 _currentBarIndex 控制显示哪个页面&#xff08;比如索引 1 就显示 NodePa…

HTML 入门教程:从零开始学习网页开发基础

一、HTML简介 1.1 什么是HTML&#xff1f; HTML全称是Hyper Text Markup Language&#xff08;超文本标记语言&#xff09;&#xff0c;由Tim Berners-Lee和同事Daniel W. Connolly于1990年创立。它是一种用于创建网页的标准标记语言&#xff0c;而不是编程语言。 1.2 HTML的…

LeetCode Hot100【4. 寻找两个正序数组的中位数】

4. 寻找两个正序数组的中位数 自己做 分析 解1&#xff1a;归并思想 class Solution { public:double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {int sum 0;double value;queue<double> value2;int i 0, j 0;if ((nums…

JobSet:Kubernetes 分布式任务编排的统一解决方案

JobSet–Kubernetes 分布式任务编排的统一解决方案 在 Kubernetes 生态中&#xff0c;分布式机器学习训练和高性能计算&#xff08;HPC&#xff09;工作负载的编排一直是技术难点。随着大语言模型&#xff08;LLM&#xff09;等大型 AI 模型的兴起&#xff0c;单主机资源已无法…

【电脑】显示器的基础知识

显示器是计算机系统中用于显示图像、文本和其他视觉内容的重要组件。它将电子信号转化为可见的图像&#xff0c;使用户可以直观地查看和操作数据。以下是关于显示器的一些详细知识&#xff1a;1. 显示器的基本类型CRT显示器&#xff08;阴极射线管显示器&#xff09;工作原理&a…

【删库跑路】一次删除pip的所有第三方库

进入命令行&#xff0c;先list看下库存pip list导出所有的第三方库至一文件列表pip freeze >requirements.txt按照列表卸载所有库pip uninstall -r requirements.txt -y再list看下&#xff0c;可见库存已清空

19.如何将 Python 字符串转换为 Slug

如何将 Python 字符串转换为 Slug(URL 友好格式) 什么是 Slug? Slug 是一种 URL 友好、便于人类阅读的字符串。只包含小写字母、数字和连字符(-)。常见于文章标题、商品名等生成的网址路径中。例如: "Hello World!" → "hello-world"1. Slugify 的…

3.2数据库-关系代数-函数依赖-范式

1、关系代数基础1、并U&#xff1a;记录合并&#xff0c;相同记录只显示一次2、交&#xff1a;两张表都有的记录。3、差&#xff1a;S1-S2 表示S1减去S2中也有的数据。笛卡尔积&#xff08;重要&#xff09;1、笛卡尔积&#xff1a;S1*S2 :列是所有列全部加起来&#xff0c;重复…

[ROS 系列学习教程] ROS动作通讯(Action):通信模型、Hello World与拓展

ROS 系列学习教程(总目录) ROS2 系列学习教程(总目录) 文章目录一、动作通讯模型二、动作通讯流程2.1 任务添加阶段2.2 任务执行阶段2.3 任务完成阶段三、Action Hello World3.1 创建并初始化功能包3.2 确定Action名称及消息格式3.3 配置编译文件3.4 实现服务端与客户端&#x…

【C++】初识C++(1)

个人主页&#xff1a;我要成为c嘎嘎大王 希望这篇小小文章可以让你有所收获&#xff01; 目录 前言 一、C的第一个程序 二、命名空间 2.1 namespace 的价值 2.2 namespace 的定义 2.2.1 正常的命名空间定义 2.2.2 命名空间可以嵌套 2.2.3 匿名命名空间 2.2.4 同名的name…

Spark Expression codegen

Expression codegen src/main/scala/org/apache/spark/sql/catalyst/expressions/Expression.scala def genCode(ctx: CodegenContext): ExprCode = {ctx.subExprEliminationExprs.get(ExpressionEquals(