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

news2024/11/17 2:28:02

一、字典树

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 的节点,直接返回false
                current = 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;
    else
        cout << "该字典树不存在" << 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 的节点,直接返回false
                current = 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.coloradmin.cn/o/1314381.html

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

相关文章

【Qt问题记录】使用QDebug类输出不带转义或双引号

问题 使用Qt进行编程时&#xff0c;需要借助输出信息验证编码的正确性。 默认情况下&#xff0c;如果输出的是字符串&#xff0c;qDebug() 会在字符串的两侧加上引号&#xff0c;有时还会转义。 如下所示&#xff1a; QString strInfo QStringLiteral("helloworld"…

[原创][R语言]股票分析实战:周级别涨幅趋势的相关性

[简介]常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、De…

网络安全Web学习记录———CTF---Web---SQL注入(GET和POST传参)例题

小白初见&#xff0c;若有问题&#xff0c;希望各位大哥多多指正~ 我的第一道web类CTF题——一起来撸猫o(•ェ•)m-CSDN博客 最开始学习CTF里的web方向时&#xff0c;每次做了题遇到类似的老是忘记之前的解法&#xff0c;所以写点东西记录一下。听大哥的话&#xff0c;就从最…

架构设计系列之常见架构(二)

五、DDD&#xff08;领域驱动设计&#xff09; 领域驱动设计&#xff08;Domain-Driven Design&#xff0c;DDD&#xff09;是一种开发思想&#xff0c;强调将软件系统的注意力集中在业务领域上&#xff0c;将领域视为应用的核心。在架构设计中&#xff0c;DDD 提供了一种不同…

cgal教程 3D Alpha Wrapping

文章目录 3D Alpha Wrapping (3D alpha 包裹)1 介绍2 方法2.1 算法2.2 保证 3 接口4 选择参数4.1 alpha4.2 Offset4.3 关于“双面”包裹的注意事项 5 性能6 例子 3D Alpha Wrapping (3D alpha 包裹) 原文地址: https://doc.cgal.org/latest/Alpha_wrap_3/index.html#Chapter_3D…

Linux unzip解压多个文件

前情介绍 最近下载了imagenet1k数据集&#xff0c;令人难受的是这里边有很多的zip包&#xff0c;我总不能一个一个解压吧&#xff0c;这就太费时了&#xff0c;有点程序员思维很重要&#xff1a;批量解压。 解决办法 假设当前目录下有多个zip文件 需要 unzip *.zip …

【FunASR】Paraformer语音识别-中文-通用-16k-离线-large-onnx

模型亮点 模型文件: damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorchParaformer-large长音频模型集成VAD、ASR、标点与时间戳功能&#xff0c;可直接对时长为数小时音频进行识别&#xff0c;并输出带标点文字与时间戳&#xff1a; ASR模型…

apt-mark工具介绍(标记或取消标记软件包,防止特定软件包被自动更新或删除)

文章目录 apt-mark工具深度解析1. apt-mark概述1.1 apt-mark定义1.2 apt-mark作用 2. apt-mark常用命令2.1 标记软件包为手动安装2.2 标记软件包为自动安装2.3 阻止软件包更新2.4 允许软件包更新 3. 疑难技术点解析3.1 如何查看软件包的标记状态3.2 如何解决软件包依赖性问题 4…

IDEA新建jdk8 spring boot项目

今天新建spring boot项目发现JDK版本最低可选17。 但是目前用的最多的还是JDK8啊。 解决办法 Server URL中设置&#xff1a; https://start.aliyun.com/设置完成后&#xff0c;又可以愉快的用jdk8创建项目了。 参考 https://blog.csdn.net/imbzz/article/details/13469117…

【C++】POCO学习总结(十七):日志系统(级别、通道、格式化、记录流)

【C】郭老二博文之&#xff1a;C目录 1、Poco::Message 日志消息 1.1 说明 所有日志消息都在Poco::Message对象中存储和传输。 头文件&#xff1a;#include “Poco/Message.h” 一条消息包含如下内容&#xff1a;优先级、来源、一个文本、一个时间戳、进程和线程标识符、可选…

每天五分钟计算机视觉:Inception网络是由多个Inception模块构成

本文重点 inception从另一种角度来提升训练结果:能更高效的利用计算资源,在相同的计算量下能提取到更多的特征,从而提升训练结果。可以简单的理解为Inception 网络是由一个一个的Inception模块构建成的,我们来看一下。 Inception模块 如上就是Inception模块的 通过一个1…

【Android逆向】记录一次某某虚拟机的逆向

导语 学了一段时间的XPosed&#xff0c;发现XPosed真的好强&#xff0c;只要技术强&#xff0c;什么操作都能实现... 这次主要记录一下我对这款应用的逆向思路 apk检查 使用MT管理器检查apk的加壳情况 发现是某数字的免费版本 直接使用frida-dexdump 脱下来后备用 应用分…

【MYSQL】事务隔离级别、脏读、不可重复读、幻读

文章目录 介绍演示脏读不可重复读可重复读幻读 不可重复读和幻读的区别 参考 作者 Guide: 事务隔离级别 美团技术团队&#xff1a; Innodb中的事务隔离级别和锁的关系 介绍 SQL 标准定义了四个隔离级别&#xff1a; READ-UNCOMMITTED(读取未提交) &#xff1a;最低的隔离级别…

虚拟机启动 I/O error in “xfs_read_agi+0x95“

1.在选择系统界面按e 进入维护模式 2.找到ro把ro改成 rw init/sysroot/bin/sh 然后按Ctrlx 3.找到坏掉的分区&#xff0c;以nvme0n1p3为例进行修复 xfs_repair -d /dev/nvme0n1p3 4.init 6 重新启动 以下情况 先umount 再修复 则修复成功

SaaS行业分析

文章目录 什么是SaaS ?SaaS的标准定义什么是软件即服务&#xff1f;SaaS与传统软件的区别 &#xff1f; SaaS行业分析你知道最赚钱的行业是什么&#xff1f;互联网带给企业的变化 SaaS与PaaS、IaaS的区别&#xff1f;IaaS&#xff08;Infrastructure as a Service&#xff09;…

消除非受检警告

在Java中&#xff0c;有一些情况下编译器会生成非受检警告&#xff08;Unchecked Warnings&#xff09;。这些警告通常与泛型、类型转换或原始类型相关。消除这些警告可以提高代码的可读性和安全性。以下是一些常见的非受检警告以及如何消除它们的例子&#xff1a; 1. 泛型类型…

js传递json数据过大的解决方案

protobufjs 使用protobuf&#xff0c;定义如下结构 Person.protobuf syntax "proto3";message Person {string name 1;int32 age 2; }Person.thrift namespace java com.example.Personstruct Person {1: required string name,2: required i32 age }使用bench…

高压电气是什么

高压电气 电工电气百科 文章目录 高压电气前言一、高压电气是什么二、高压电气的类别三、高压电气的作用原理总结前言 高压电气在电力系统中起着重要的作用,它能够将电能有效地输送和分配到各个用户,为社会和工业生产提供稳定可靠的电力供应。然而,高压电气系统也需要注意安…

Jwt令牌过滤器的下发和拦截(创建在前面)

创建Jwt令牌的方法在前面&#xff1a; JWT令牌的作用和生成https://blog.csdn.net/m0_71149935/article/details/135002840?spm1001.2014.3001.5501令牌的下发&#xff1a; 说明&#xff1a; 只用在浏览器访问服务器的时候校验账户信息是否正确&#xff0c;正确就创建Jwt令…

网站服务器/域名/备案到底有什么关联?

​  在一个网站的组成中&#xff0c;网站服务器、域名、备案这几个要素是要被常提到的。在谈及三者关联之前&#xff0c;我们先了解下三者的各自概念。 域名&#xff1a;它是网站的唯一标识符&#xff0c;通俗理解来说就是用户在浏览器地址栏中输入的网址。一般来说&#xff…