数据结构:Trie(前缀树/字典树)

news2024/11/26 22:41:18

文章目录

  • 一、介绍Trie
    • 1.1、Trie的结点结构
    • 1.2、Trie的整体结构
  • 二、Trie的操作
    • 2.1、Trie插入操作
    • 2.2、Trie查找操作
    • 2.3、Trie前缀匹配操作
    • 2.4、Trie删除操作
  • 三、实战
    • 3.1、实现Trie(前缀树)

一、介绍Trie

  Trie 又称字典树、前缀树和单词查找树,如果关键词是数字序列,则称数字查找树,是一颗非典型的多叉树模型,即每个结点的分支数量可能为多个。Trie这个名字取自“retrieval”,检索,读音和 try 相同。
  百度百科:Trie

1.1、Trie的结点结构

Trie的结点结构是这样的:

strcut TrieNode{
	bool isEnd;//该结点是否是一个串的结束。
	TrieNode * next[26];//字母映射表,结点个数跟一个串中包含的字符种树有关
	TrieNode(){//默认为空
		isEnd=false;//默认不是一个串的结束
		for(auto & i:next) i=nullptr;
	}
};

  需要注意的是,一个节点即使标记为某个串的结束(isEnd=true),也可以是其他更长串的前缀。这意味着,该节点可以有子节点,它们代表着以当前串为前缀的其他串。这个特性使得Trie成为一种极其有效的数据结构,用于处理具有共同前缀的字符串集合。

1.2、Trie的整体结构

在这个结点结构下,包含三个单词 “sea”,“sells”,“she” 的 Trie 会长啥样呢?
在这里插入图片描述
为了方便理解我们可以画成这样(也是实际树结构):
在这里插入图片描述
  根据整体结构,我们可以理解Trie实际上用边表示字符,每次选择一个子节点就可以选定一个字符,如果某结点对应位置为空,则说明Trie不包含 从根到该结点的边连接成的串加上该位置对应的字符 构成的 前缀。

二、Trie的操作

首先创建根结点,初始时根结点为空。

TrieNode * root=new TrieNode();

2.1、Trie插入操作

向字典树Trie中插入一个单词word,它保证使用了已有字典树的最长单词前缀:

  1. 初始化:从Trie的根节点开始。
  2. 遍历单词中的每个字符:对于单词 word 中的每个字符 c:
    • 检查当前节点是否存在字符 c 对应的子节点。
    • 如果存在,移动到该子节点,继续查找下一个字符。
    • 如果不存在,查找则创建该子节点,并移动到该子节点。
  3. 检查单词完全匹配:在成功遍历单词 word 中的所有字符后,将最后一个节点标记isEnd=true
void insert(string word){
	TrieNode * node=root;
	for(char c:word){
		if(node->next[c-'a']==nullptr){
			node->next[c-'a']=new TrieNode();
		}
		node=node->next[c-'a'];
	}
	node->isEnd=true;//串的结束只跟插入时的串有关。
	return;
}

2.2、Trie查找操作

判断字典树Trie中是否包含单词word:

  1. 初始化:从Trie的根节点开始。
  2. 遍历单词中的每个字符:对于单词 word 中的每个字符 c:
    • 检查当前节点是否存在字符 c 对应的子节点。
    • 如果存在,移动到该子节点,继续查找下一个字符。
    • 如果不存在,查找失败,返回 false(表示Trie中不存在单词 word)。
  3. 检查单词完全匹配:在成功遍历单词 word 中的所有字符后,需要检查当前节点是否标记为某个单词的结尾。
    • 如果当前节点标记为单词的结尾,则查找成功,返回 true。
    • 如果当前节点未标记为单词的结尾,则意味着Trie中没有完全匹配的单词,返回 false。
bool search(string word){
	TrieNode * node=root;
	for(char c:word){
		if(node->next[c-'a']==nullptr)
			return false;
		node=node->next[c-'a'];
	}
	return node->isEnd;//if(node->isEnd) return true;
}

2.3、Trie前缀匹配操作

判断字典树Trie中是否有以prefix为前缀的单词,由于Trie是通过插入结点生成的,因此只要一颗Trie能遍历完所有prefix中的字符,那么它必然是含有以prefix为前缀的单词的。它的实现和search操作类似:

  1. 初始化:从Trie的根节点开始。
  2. 遍历单词中的每个字符:对于单词 prefix 中的每个字符 c:
    • 检查当前节点是否存在字符 c 对应的子节点。
    • 如果存在,移动到该子节点,继续查找下一个字符。
    • 如果不存在,查找失败,返回 false。
  3. 检查单词完全匹配:在成功遍历单词 prefix 中的所有字符后,即所有字符均匹配,因此存在这样的前缀,返回true。
bool startsWith(string prefix){
	TrieNode * node=root;
	for(char c:prefix){
		if(node->next[c-'a']==nullptr)
			return false;
		node=node->next[c-'a'];
	}
	return true;
}

2.4、Trie删除操作

从Trie中删除一个串word时,我们应当从根结点把该路径上的结点依次删除,直至某结点的儿子不为空 或者 为根结点时,则不再删除。如果采用删除操作可以在树结点中记录儿子个数,这样可以快速判断是否还有儿子。可以通过在Trie结构中加入char ch,表示当前结点的字符应当是什么,可以快速找到儿子位置。这里采用整个文章的结构,所以不记录。

  1. 初始化节点数组:为了存储删除路径上的每个节点,函数首先创建了一个指针数组 path,大小为待删除字符串 str 的长度。
  2. 遍历Trie以找到字符串:函数遍历Trie以寻找与 str 匹配的字符串,同时在 path 数组中记录遍历过程中访问的每个节点。
  3. 检查并删除字符串
    • 如果未找到完全匹配的字符串(即在Trie中不存在该字符串),函数返回 false。
      找到后,标记其isEnd=true。从字符串的末尾开始向上回溯,检查每个节点是否有其他子节点。
    • 如果有其他子节点或该节点为根,说明当前节点是其他字符串的前缀,函数结束,返回 true。
    • 如果没有其他子节点,并且isEnd==true,则函数结束,返回true。
    • 如果没有其他子节点,并且isEnd!=true,删除当前节点,并将其父节点中对应的指针设置为 nullptr。
bool delete(string word){
	vector<TrieNode *> path;
	TrieNode * node=root;
	path.push_back(node);
	for(auto &c:word){
		if(node->next[c-'a']==nullptr)
			return false;
		node=node->next[c-'a'];
		path.push_back(node);
	}
	if(path.back()->isEnd==false) return false;
	path.back()->isEnd=false;//可能非叶子结点,不能直接删除,先标记为不是串的结尾
	bool flag=true;
	while(flag&&path.size()>1){//回溯向上,判断是否删除结点
		if(path.back()->isEnd) break;//如果是串的结尾 那么也不能再删除了
		TrieNode * child=path.back();
		for(auto &i=child->next){//查看其是否有儿子,可以通过为每一个Trie结点维护一个儿子数量,来快速判断。
			if(i!=nullptr) {flag=false;break;}//不可删除
		}
		if(flag){//没儿子
			path.pop_back();
			TrieNode * fa=path.back();
			for(auto & i=fa->next)//找到儿子,并删除,可以通过在Trie结构中加入char ch,表示当前结点的字符应当是什么,可以快速找到儿子位置。
				if(i==child) {delete child;i=nullptr;break;}//删了之后break;不break会访问野指针。~
		}
	}
	return true;
}

这里需要注意指针 和 指针指向的内容 的区别:

  • i==child:指的是i和child的值相同,指针类型 相当于i和child指向的地址相同。
  • delete child:指的是回收child指向的内容。
  • i=nullptr:i是引用,实际上是修改i引用的fa->next[x]=nullptr。

三、实战

3.1、实现Trie(前缀树)

题目链接:LeetCode:208.实现Trie(前缀树)

class Trie {
public:
    Trie() {
        isEnd=false;
        for(auto &i:next) i=nullptr;
    }
    
    void insert(string word) {
        Trie * node=this;//this指针表示被插入的字典树的根结点。
        for(char c:word){
            if(node->next[c-'a']==nullptr)
                node->next[c-'a']=new Trie();
            node=node->next[c-'a'];
        }
        node->isEnd=true;
        return;
    }
    
    bool search(string word) {
        Trie * node=this;
        for(char c:word){
            if(node->next[c-'a']==nullptr)  return false;
            node=node->next[c-'a'];
        }
        return node->isEnd;
    }
    
    bool startsWith(string prefix) {
        Trie * node=this;
        for(char c:prefix){
            if(node->next[c-'a']==nullptr)  return false;
            node=node->next[c-'a'];
        }
        return true;
    }
private:
    bool isEnd;
    Trie * next[26];
};
/*使用样例:始终对root进行插入、查找、前缀匹配查找操作
Trie * root=new Trie;
root->insert(val);
root->insert(val2);
root->insert(val3);
if(root->search(val4)) cout<<true<<endl;
if(root->startsWith(val4)) cout<<true<<endl;
*/

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

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

相关文章

短视频素材那里来?五大平台让你的视频大放异彩!

哈喽&#xff01;短视频创作者们&#xff0c;是不是在寻找那些能让你的剪辑视频更加闪耀的素材&#xff1f;别着急&#xff0c;今天我要为你们带来五个超棒的视频素材网站&#xff0c;让你的作品在抖音、快手上大放异彩&#xff0c;成为众人羡慕的焦点&#xff01; 蛙学网&…

python电商结合双轨制

最近又重新整合翻看以前的数据&#xff0c;图片&#xff0c;绘画&#xff0c;还有各种编程代码&#xff0c;python,leetcode,还有关于商业方面的一些见解,想起了大学时候和同学们并肩作战&#xff0c;熬夜编码的时光。还有大数据&#xff0c;八爪鱼爬虫。 下面是我的手稿电商打…

输出单链表倒数第K个结点值

方法一&#xff1a; 两次遍历链表。第一次遍历&#xff0c;计算链表长度&#xff0c;然后计算链表倒数第m个结点的正数位置k&#xff0c;判断位置是否合法&#xff0c;如果不合法&#xff0c;输出NOT FOUND&#xff0c;否则&#xff0c;进行第二次遍历链表&#xff0c;查找链表…

【初阶数据结构】——牛客:CM11 链表分割

文章目录 1. 题目介绍2. 思路分析3. 代码实现 1. 题目介绍 链接: link 这道题是给我们一个链表和一个值X &#xff0c;要求我们以给定值x为基准将链表分割成两部分&#xff0c;所有小于x的结点排在大于或等于x的结点之前。 最终返回重新排列之后的链表的头指针。 2. 思路分析…

根据实例逐行分析NIO到底在做什么

Selector&#xff08;选择器&#xff09;是 Channel 的多路复用器&#xff0c;它可以同时监控多个 Channel 的 IO 状况&#xff0c;允许单个线程来操作多个 Channel。Channel在从Buffer中获取数据。 选择器、通道、缓冲池是NIO的核心组件。 一、新建选择器 此时选择器内只包含…

Python学习笔记-简单案例实现多进程与多线程

Python 的多进程与多线程是并发编程的两种重要方式&#xff0c;用于提高程序的执行效率。它们各自有不同的特点和适用场景。 多进程&#xff08;Multiprocessing&#xff09; 概念&#xff1a; 多进程是指操作系统中同时运行多个程序实例&#xff0c;每个实例称为一个进程。…

FA模型切换Stage模型组件切换之ServiceAbility切换DataAbility切换

ServiceAbility切换 FA模型中的ServiceAbility对应Stage模型中的ServiceExtensionAbility。Stage模型下的ServiceExtensionAbility为系统API&#xff0c;只有系统应用才可以创建。因此&#xff0c;FA模型的ServiceAbility的切换&#xff0c;对于系统应用和三方应用策略有所不同…

Java线程池工作原理浅析

为什么要用线程池&#xff1f; 1、线程属于稀缺资源&#xff0c;它的创建会消耗大量系统资源 2、线程频繁地销毁&#xff0c;会频繁地触发GC机制&#xff0c;使系统性能降低 3、多线程并发执行缺乏统一的管理与监控 线程池的使用 线程池的创建使用可通过Executors类来完成…

Clip算法解读

论文地址&#xff1a;https://arxiv.org/pdf/2103.00020.pdf 代码地址&#xff1a;https://github.com/OpenAI/CLIPz 中文clip代码&#xff1a;https://gitcode.com/OFA-Sys/Chinese-CLIP/overview 一、动机 主要解决的问题&#xff1a; 超大规模的文本集合训练出的 NLP 模…

vue属性与方法

vue属性与方法 计算属性v-model指令——表单的实现样式绑定 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&g…

前端面试拼图-数据结构与算法(二)

摘要&#xff1a;最近&#xff0c;看了下慕课2周刷完n道面试题&#xff0c;记录下... 1. 求一个二叉搜索树的第k小值 二叉树(Binary Tree) 是一棵树 每个节点最多两个子节点 树节点的数据结构{value, left?, right?} 二叉树的遍历 前序遍历&#xff1a;root→left→right 中…

Java类与对象:从概念到实践的全景解析!

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 文章专栏&#xff1a;javaSE的修炼之路 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 1、类的定义格式 在java中定义类时需要用到…

记录一个写自定义Flume拦截器遇到的错误

先说结论&#xff1a; 【结论1】配置文件中包名要写正确 vim flume1.conf ... a1.sources.r1.interceptors.i1.type com.atguigu.flume.interceptor.MyInterceptor2$MyBuilder ... 标红的是包名&#xff0c;表黄的是类名&#xff0c;标蓝的是自己加的内部类名。这三个都…

大话设计模式之工厂模式

工厂模式&#xff08;Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的最佳方式&#xff0c;而无需指定将要创建的对象的确切类。通过使用工厂模式&#xff0c;我们可以将对象的创建和使用分离&#xff0c;从而使代码更具灵活性和可维护性。…

Python之Opencv教程(1):读取图片、图片灰度处理

1、Opencv简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个用于计算机视觉和图像处理的开源库&#xff0c;提供了丰富的图像处理、计算机视觉和机器学习功能。它支持多种编程语言&#xff0c;包括C、Python、Java等&#xff0c;广泛应用于图像处…

Unity 学习日记 13.地形系统

下载源码 UnityPackage 1.地形对象Terrain 目录 1.地形对象Terrain 2.设置地形纹理 3.拔高地形地貌 4. 绘制树和草 5.为地形加入水 6.加入角色并跑步 7.加入水声 右键创建3D地形&#xff1a; 依次对应下面的按钮 || 2.设置地形纹理 下载资源包 下载资源包后&#x…

【微服务】软件架构的演变之路

目录 单体式架构的时代单体式架构(Monolithic)优点缺点适用场景单体式架构面临诸多问题1.宽带提速&#xff0c;网民增多2.Web2.0时代的特点问题描述优化方向 集群优点缺点适用场景搭建集群后面临诸多问题用户请求问题用户的登录信息数据查询 改进后的架构 垂直架构优点缺点 分布…

OSPF基本原理和概念

文章目录 背景知识OSPF协议概述&#xff1a;OSPF区域的表示OSPF 骨干区域 –区域0OSPF 非骨干区域 -非0区域OSPF的五种区域类型OSPF工作原理OSPF 的报文类型OSPF邻居表中的七个状态 总结 背景知识 一台路由设备如何获取其他网段的路由&#xff0c;并加入到路由表中 直连路由 …

【Java】LinkedList模拟实现

目录 整体框架IMyLinkedList接口IndexNotLegalException异常类MyLinkedList类成员变量(节点信息)addFirst(头插)addLast(尾插)在指定位置插入数据判断是否存在移除第一个相等的节点移除所有相等的节点链表的长度打印链表释放回收链表 整体框架 IMyLinkedList接口 这个接口用来…

IDE/VS2015和VS2017帮助文档MSDN安装和使用

文章目录 概述VS2015MSDN离线安装离线MSDN的下载离线MSDN安装 MSDN使用方法从VS内F1启动直接启动帮助程序跳转到了Qt的帮助网页 VS2017在线安装MSDN有些函数在本地MSDN没有帮助&#xff1f;切换中英文在线帮助文档 概述 本文主要介绍了VS集成开发环境中&#xff0c;帮助文档MS…