数据结构(7.5)-- 树扩展之字典树

一、字典树

1、字典树介绍

字典树,也称为“前缀树”,是一种特殊的树状数据结构,对于解决字符串相关问题非常有效。典型

用于统计、排序、和保存大量字符串。所以经常被搜索引擎系统用于文本词频统计。它的优点是:

利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树

高。

2、字典树的性质

(1)根节点不包含字符,除了根节点每个节点都只包含一个字符。root节点不含字符这样做的目的是为了能够包括所有字符串。

(2)从根节点到某一个节点,路过字符串起来就是该节点对应的字符串。

(3)每个节点的子节点字符不同,也就是找到对应单词、字符是唯一的。

3、字典树的实现

(1)定义多叉树--孩子表示法

struct TreeNode {ELEMENT_TYPE value;    //结点值TreeNode* children[NUM];    //孩子结点
};

(2) 定义字典树

int size = 26;
struct TrieNode {bool isEndOfWord; //记录该结点是否是一个串的结束TrieNode* children[SIZE]; //字母映射表
};

二、面试中关于字典树的常见问题

1、计算字典树中的总单词数

题目:创建一颗字典树,并且计算该字典树中的总单词数。如下字典树,[“bag”, “ban”, “bat”, “big”,“bil”,“bit”],单词数为6。

思路:

首先要创建一颗字典树,在每个节点上,设置一个标记来表示该节点是否是一个单词的结束。

再向字典树中插入单词,从根节点开始,递归地遍历字典树的所有子节点。

最后计算单词数:对于每个子节点,如果其标记为结束,则将计数器加1。最终,计数器的值就是字典树中的总单词数。

#include<iostream>
using namespace std;const int ALPHABET_SIZE = 26;
// TrieNode表示字典树中的节点
struct TrieNode
{bool isEndOfWord; //用于标记该节点是否是一个单词的结束TrieNode* children[ALPHABET_SIZE]; //包含26个子节点的数组// 构造函数TrieNode():isEndOfWord(false){for(int i = 0; i < ALPHABET_SIZE; i++){children[i] = nullptr;}}
};class Trie{private:TrieNode* root;public:Trie(){root = new TrieNode();}// 向字典树中插入单词void insert(string word){TrieNode* current = root;// 遍历字符串中的字符for(char ch: word){int index = ch - 'a';if(current->children[index] == nullptr)current->children[index] = new TrieNode();current = current->children[index];}// 遍历结束,标记该节点单词结束current->isEndOfWord = true;}// 计算字典树中的总单词数int countWords(){int count = 0;countWordsDFS(root, count);return count;}private:// 深度优先搜索(DFS)递归地遍历字典树的所有节点,并在遇到结束节点时将计数器加1。void countWordsDFS(TrieNode* node, int& count){if(node == NULL)return;if(node->isEndOfWord) count += 1;for(int i = 0; i < ALPHABET_SIZE; i++){if(node->children[i] != NULL)countWordsDFS(node->children[i], count);}}};int main(){Trie trie;trie.insert("bag");trie.insert("ban");trie.insert("bat");trie.insert("big");trie.insert("bil");trie.insert("bit");int count = trie.countWords();cout << "字典树中的总单词数:" << count << endl;
}

2、查找字典树中某个单词是否存在 

题目:输入ban,返回true;输入bad,返回False。

思路:插入操作类似。

从字典树的根节点依次遍历单词中的字符,如果当前节点的子节点中,不存在键为 ch 的节点,则说明不存在该单词,直接返回 False。如果当前节点的子节点中,存在键为 ch 的节点,则令当前节点指向新建立的节点,然后继续查找下一个字符。在单词处理完成时,判断当前节点是否有单词结束标记,如果有,则说明字典树中存在该单词,返回 True。否则,则说明字典树中不存在该单词,返回 False。

#include<iostream>
using namespace std;const int ALPHABET_SIZE = 26;
// TrieNode表示字典树中的节点
struct TrieNode
{bool isEndOfWord; //用于标记该节点是否是一个单词的结束TrieNode* children[ALPHABET_SIZE]; //包含26个子节点的数组// 构造函数TrieNode():isEndOfWord(false){for(int i = 0; i < ALPHABET_SIZE; i++){children[i] = nullptr;}}
};class Trie{private:TrieNode* root;public:Trie(){root = new TrieNode();}// 向字典树中插入单词void insert(string word){TrieNode* current = root;// 遍历字符串中的字符for(char ch: word){int index = ch - 'a';if(current->children[index] == nullptr)current->children[index] = new TrieNode();current = current->children[index];}// 遍历结束,标记该节点单词结束current->isEndOfWord = true;}// 查找字典树中某个单词是否存在bool searchWord(string word){TrieNode* current = root;for(char ch: word){int index = ch - 'a';if(current->children[index] == NULL)return false; //如果当前节点的子节点中,不存在键为 ch 的节点,直接返回falsecurrent = current->children[index];}// 判断当前节点是否为空,并且是否有单词结束标记return (current->isEndOfWord & current != NULL);}};int main(){Trie trie;trie.insert("bag");trie.insert("ban");trie.insert("bat");trie.insert("big");trie.insert("bil");trie.insert("bit");string word = "bad";bool b = trie.searchWord(word);if(b)cout << "该字典树存在" << word;elsecout << "该字典树不存在" << word;}

3、查找字典树中某个前缀是否存在

在字典树中查找某个前缀是否存在,和字典树的查找单词操作一样,不同点在于最后不需要判断是否有单词结束标记。

// 查找字典树中某个前缀是否存在bool searchWord(string word){TrieNode* current = root;for(char ch: word){int index = ch - 'a';if(current->children[index] == NULL)return false; //如果当前节点的子节点中,不存在键为 ch 的节点,直接返回falsecurrent = current->children[index];}// 判断当前节点是否为空return current;}

4、打印存储在字典树中的所有单词

题目:如下字典树,打印[ bag,ban,bat,big,bil,bit ]。

思路:在哈希遍历树的完整路径中,我们使用先序遍历逐步构建叶子节点的路径。本题类似,使用

深度遍历递归地遍历字典树的所有子节点,并存储每个叶子节点的字符串。遇到叶子节点(即

isEndOfWord=true),则打印存储在容器里的字符串(字符串组合起来就是一个完整单词)。

#include<iostream>
using namespace std;const int ALPHABET_SIZE = 26;
// TrieNode表示字典树中的节点
struct TrieNode
{bool isEndOfWord; //用于标记该节点是否是一个单词的结束TrieNode* children[ALPHABET_SIZE]; //包含26个子节点的数组// 构造函数TrieNode():isEndOfWord(false){for(int i = 0; i < ALPHABET_SIZE; i++){children[i] = nullptr;}}
};class Trie{private:TrieNode* root;public:Trie(){root = new TrieNode();}// 向字典树中插入单词void insert(string word){TrieNode* current = root;// 遍历字符串中的字符for(char ch: word){int index = ch - 'a';if(current->children[index] == nullptr)current->children[index] = new TrieNode();current = current->children[index];}// 遍历结束,标记该节点单词结束current->isEndOfWord = true;}void printWord(){string arr;print(root, arr);}private: void print(TrieNode* node, string& arr){if(node->isEndOfWord){for(auto a: arr)cout << a;cout << "," ;}for(int i = 0; i < ALPHABET_SIZE; i++){if(node->children[i] != NULL){char ch = i + 'a';arr.push_back(ch);print(node->children[i], arr);}}arr.pop_back();}
};int main(){Trie trie;trie.insert("bag");trie.insert("ban");trie.insert("bat");trie.insert("big");trie.insert("bil");trie.insert("bit");trie.printWord();
}

 

5、使用字典树对数组的元素进行排序

题目:

input:arr = ['apple', 'banana', 'application']

output:arr = ['apple', 'application', 'banana']

思路:同第二题,因为字典树节点的顺序已经确定,所以,遍历出来的单词即是排序后的单词组。

#include<iostream>
#include<vector>
using namespace std;const int ALPHABET_SIZE = 26;
// TrieNode表示字典树中的节点
struct TrieNode
{bool isEndOfWord; //用于标记该节点是否是一个单词的结束TrieNode* children[ALPHABET_SIZE]; //包含26个子节点的数组// 构造函数TrieNode():isEndOfWord(false){for(int i = 0; i < ALPHABET_SIZE; i++){children[i] = nullptr;}}
};class Trie{private:TrieNode* root;public:Trie(){root = new TrieNode();}// 向字典树中插入单词void insert(string word){TrieNode* current = root;// 遍历字符串中的字符for(char ch: word){int index = ch - 'a';if(current->children[index] == nullptr)current->children[index] = new TrieNode();current = current->children[index];}// 遍历结束,标记该节点单词结束current->isEndOfWord = true;}vector<string> Order(){string arr;vector<string> s;traverse(root, arr, s);return s;}private: // arr:连接字符串组成的单词,res:排序后的数组void traverse(TrieNode* node, string& arr, vector<string>& res){if(node->isEndOfWord){res.push_back(arr);}for(int i = 0; i < ALPHABET_SIZE; i++){if(node->children[i] != NULL){char ch = i + 'a';arr.push_back(ch);traverse(node->children[i], arr, res);}}arr.pop_back();}
};int main(){Trie trie;vector<string> arr = { "apple", "banana", "application" };for (const string& word : arr)trie.insert(word);vector<string> sortedArr = trie.Order();std::cout << "排序后的数组元素:" << std::endl;for (const std::string& word : sortedArr)std::cout << word << std::endl;
}

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

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

相关文章

四十二、Redis

目录 一、简介 二、Redis基础 三、Redis的持久化 四、Redis主从、哨兵、分片集群安装 五、Redis主从 六、Redis哨兵 七、Redis分片集群 一、简介 Redis是一个内存中的数据结构存储系统&#xff0c;可以用作数据库、缓存和消息中间件。它的数据结构包括字符串、列表、集合…

MySQL数据库 入门

目录 一、MySQL概述 二、MySQL安装 安装数据库 配置数据库 启动停止数据库 客户端连接数据库 三、数据模型 四、SQL 一、MySQL概述 先来讲解三个概念&#xff1a;数据库、数据库管理系统、 SQL 。 而目前主流的关系型数据库管理系统的市场占有率排名如下&#xff1a; …

Linux--权限问题(2)

目录 前文 前言 1. 文件的权限 1.1 文件的访问者分类 1.2 文件类型和访问权限&#xff08;事物属性&#xff09; 2. 如何修改文件的权限 3.对比权限有无的表现 4.修改用户角色 5.修改权限的第二种做法 6.目录的权限 7.默认权限 前文 Linux--权限问题&#xff08;1&#…

四十七、Redis分片集群

目录 一、分片集群结构 二、散列插槽 1、Redis如何判断某个key应该在哪个实例&#xff1f; 2、如何将同一类数据固定的保存在同一个Redis实例&#xff1f; 三、集群伸缩 四、故障转移 1、当集群中有一个master宕机时 &#xff08;1&#xff09;自动转移 &#xff08;2&…

Winform高效获取控件(Control)方法 + 源码分析

背景&#xff1a;风好大&#xff0c;睡觉有点怕&#xff0c;起床敲代码了 之前学的都是都是通过遍历控件&#xff08;Controls&#xff09;&#xff0c;判断控件名是否相等来获取Control 其实直接通过:Controls["控件名"]&#xff0c;就可以获得需要的控件 为什么呢…

【STM32独立看门狗(IWDG) 】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、看门狗是什么&#xff1f;1.简介2. 主要功能3.独立看门狗如何工作4.寄存器写保护5.看门狗 看门时间 二、使用步骤1.开启时钟2.初始化看门狗3.开启看门狗4.喂…

使用 TensorFlow 创建生产级机器学习模型(基于数据流编程的符号数学系统)——学习笔记

资源出处&#xff1a;初学者的 TensorFlow 2.0 教程 | TensorFlow Core (google.cn) 前言 对于新框架的学习&#xff0c;阅读官方文档是一种非常有效的方法。官方文档通常提供了关于框架的详细信息、使用方法和示例代码&#xff0c;可以帮助你快速了解和掌握框架的使用。 如…

MFC逆向之CrackMe Level3 过反调试 + 写注册机

今天我来分享一下,过反调试的方法以及使用IDA还原代码 写注册机的过程 由于内容太多,我准备分为两个帖子写,这个帖子主要是写IDA还原代码,下一个帖子是写反调试的分析以及过反调试和异常 这个CrackMe Level3是一个朋友发我的,我也不知道他在哪里弄的,我感觉挺好玩的,对反调试…

DS冲刺整理做题定理(四)查找与排序

最后一期更新&#xff0c;考试之前应该不会再出该专题了&#xff0c;之后有时间会出一些有关链表的代码题&#xff0c;其他章节只挑选重点的总结~ 一.查找 1.顺序查找 又被称为线性查找&#xff0c;对顺序表和链表都使用~基本思想是从某一端开始&#xff0c;逐个检查关键字是否…

VR云游打造沉浸式文旅新体验,延伸智慧文旅新业态

从“跃然纸上”到“映入眼帘”&#xff0c;随着国家数字化战略的深入实施&#xff0c;文旅产业的数字化转型正在不断加快&#xff0c;“沉浸式”逐渐成为了文旅消费新热点。VR技术与文旅产业相融合&#xff0c;新产品、新模式、新业态不断涌现&#xff0c;文旅资源逐渐“活”起…

智能插座是什么

智能插座 电工电气百科 文章目录 智能插座前言一、智能插座是什么二、智能插座的类别三、智能插座的原理总结 前言 智能插座的应用广泛&#xff0c;可以用于智能家居系统中的电器控制&#xff0c;也可以应用在办公室、商业场所和工业控制中&#xff0c;方便快捷地实现电器的远…

Milesight VPN server.js 任意文件读取漏洞(CVE-2023-23907)

0x01 产品简介 MilesightVPN 是一款软件&#xff0c;一个 Milesight 产品的 VPN 通道设置过程更加完善&#xff0c;并可通过网络服务器界面连接状态。 0x02 漏洞概述 MilesightVPN server.js接口处存在文件读取漏洞&#xff0c;攻击者可通过该漏洞读取系统重要文件&#xff…