Trie树详解及其应用

数据结构与算法:前缀树(字典树、Trie树) 前缀树 阅读详情
一、知识简介  
      最近在看字符串算法了,其中字典树、AC自动机和后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用。
      字典树(Trie)可以保存一些字符串->值的对应关系。基本上,它跟 Java 的 HashMap 功能相同,都是 key-value 映射,只不过 Trie 的 key 只能是字符串。
  Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,与 Trie 中保存了多少个元素无关。Hash 表号称是 O(1) 的,但在计算 hash 的时候就肯定会是 O(k) ,而且还有碰撞之类的问题;Trie 的缺点是空间消耗很高。
  至于Trie树的实现,可以用数组,也可以用指针动态分配,我做题时为了方便就用了数组,静态分配空间。
      Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
      Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
Trie树的基本性质可以归纳为: 
(1)根节点不包含字符,除根节点意外每个节点只包含一个字符。
(2)从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。 
(3)每个节点的所有子节点包含的字符串不相同。
Trie树有一些特性:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
5)插入查找的复杂度为O(n),n为字符串长度。
基本思想(以字母树为例):
1、插入过程
对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入Trie树。
2、查询过程
同样的,从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。

二、字典树的数据结构:
    利用串构建一个字典树,这个字典树保存了串的公共前缀信息,因此可以降低查询操作的复杂度。
    下面以英文单词构建的字典树为例,这棵Trie树中每个结点包括26个孩子结点,因为总共有26个英文字母(假设单词都是小写字母组成)。
    则可声明包含Trie树的结点信息的结构体:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. typedef struct Trie_node  
  2. {  
  3.     int count;                    // 统计单词前缀出现的次数  
  4.     struct Trie_node* next[26];   // 指向各个子树的指针  
  5.     bool exist;                   // 标记该结点处是否构成单词    
  6. }TrieNode , *Trie;  
      其中next是一个指针数组,存放着指向各个孩子结点的指针。
      如给出字符串"abc","ab","bd","dda",根据该字符串序列构建一棵Trie树。则构建的树如下:

Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',因此根结点中数组next下标为'a'-97的值不为NULL,其他同理,构建的Trie树如图所示,红色结点表示在该处可以构成一个单词。很显然,如果要查找单词"abc"是否存在,查找长度则为O(len),len为要查找的字符串的长度。而若采用一般的逐个匹配查找,则查找长度为O(len*n),n为字符串的个数。显然基于Trie树的查找效率要高很多。
如上图中:Trie树中存在的就是abc、ab、bd、dda四个单词。在实际的问题中可以将标记颜色的标志位改为数量count等其他符合题目要求的变量。
已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。下面对比3种方法:

1、 最容易想到的:即从字符串集中从头往后搜,看每个字符串是否为字符串集中某个字符串的前缀,复杂度为O(n^2)。

2、 使用hash:我们用hash存下所有字符串的所有的前缀子串。建立存有子串hash的复杂度为O(n*len)。查询的复杂度为O(n)* O(1)= O(n)。

3、 使用Trie:因为当查询如字符串abc是否为某个字符串的前缀时,显然以b、c、d....等不是以a开头的字符串就不用查找了,这样迅速缩小查找的范围和提高查找的针对性。所以建立Trie的复杂度为O(n*len),而建立+查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程,hash就不能实现这个功能。所以总的复杂度为O(n*len),实际查询的复杂度只是O(len)。
三、Trie树的操作
    在Trie树中主要有3个操作,插入、查找和删除。一般情况下Trie树中很少存在删除单独某个结点的情况,因此只考虑删除整棵树。
1、插入
  假设存在字符串str,Trie树的根结点为root。i=0,p=root。
  1)取str[i],判断p->next[str[i]-97]是否为空,若为空,则建立结点temp,并将p->next[str[i]-97]指向temp,然后p指向temp;
   若不为空,则p=p->next[str[i]-97];
  2)i++,继续取str[i],循环1)中的操作,直到遇到结束符'\0',此时将当前结点p中的 exist置为true。
2、查找
  假设要查找的字符串为str,Trie树的根结点为root,i=0,p=root
  1)取str[i],判断判断p->next[str[i]-97]是否为空,若为空,则返回false;若不为空,则p=p->next[str[i]-97],继续取字符。
  2)重复1)中的操作直到遇到结束符'\0',若当前结点p不为空并且 exist 为true,则返回true,否则返回false。
3、删除
  删除可以以递归的形式进行删除。
前缀查询的典型应用:
http://acm.hdu.edu.cn/showproblem.php?pid=1251
[cpp]  view plain copy 派生到我的代码片
  1. #include<iostream>  
  2. #include<cstring>  
  3. using namespace std;  
  4.   
  5. typedef struct Trie_node  
  6. {  
  7.     int count;                    // 统计单词前缀出现的次数  
  8.     struct Trie_node* next[26];   // 指向各个子树的指针  
  9.     bool exist;                   // 标记该结点处是否构成单词    
  10. }TrieNode , *Trie;  
  11.   
  12. TrieNode* createTrieNode()  
  13. {  
  14.     TrieNode* node = (TrieNode *)malloc(sizeof(TrieNode));  
  15.     node->count = 0;  
  16.     node->exist = false;  
  17.     memset(node->next , 0 , sizeof(node->next));    // 初始化为空指针  
  18.     return node;  
  19. }  
  20.   
  21. void Trie_insert(Trie root, char* word)  
  22. {  
  23.     Trie node = root;  
  24.     char *p = word;  
  25.     int id;  
  26.     while( *p )  
  27.     {  
  28.         id = *p - 'a';  
  29.         if(node->next[id] == NULL)  
  30.         {  
  31.             node->next[id] = createTrieNode();  
  32.         }  
  33.         node = node->next[id];  // 每插入一步,相当于有一个新串经过,指针向下移动  
  34.         ++p;  
  35.         node->count += 1;      // 这行代码用于统计每个单词前缀出现的次数(也包括统计每个单词出现的次数)  
  36.     }  
  37.     node->exist = true;        // 单词结束的地方标记此处可以构成一个单词  
  38. }  
  39.   
  40. int Trie_search(Trie root, char* word)  
  41. {  
  42.     Trie node = root;  
  43.     char *p = word;  
  44.     int id;  
  45.     while( *p )  
  46.     {  
  47.         id = *p - 'a';  
  48.         node = node->next[id];  
  49.         ++p;  
  50.         if(node == NULL)  
  51.             return 0;  
  52.     }  
  53.     return node->count;  
  54. }  
  55.   
  56. int main(void)  
  57. {  
  58.     Trie root = createTrieNode();     // 初始化字典树的根节点  
  59.     char str[12] ;  
  60.     bool flag = false;  
  61.     while(gets(str))  
  62.     {  
  63.         if(flag)  
  64.             printf("%d\n",Trie_search(root , str));  
  65.         else  
  66.         {  
  67.             if(strlen(str) != 0)  
  68.             {  
  69.                 Trie_insert(root , str);  
  70.             }  
  71.             else  
  72.                 flag = true;  
  73.         }  
  74.     }  
  75.   
  76.     return 0;  
  77. }  
字典树的查找
http://acm.hdu.edu.cn/showproblem.php?pid=1075

[cpp]  view plain copy 派生到我的代码片
  1. #include<iostream>  
  2. #include<cstring>  
  3. using namespace std;  
  4.   
  5. typedef struct Trie_node  
  6. {  
  7.     int count;                    // 统计单词前缀出现的次数  
  8.     struct Trie_node* next[26];   // 指向各个子树的指针  
  9.     bool exist;                   // 标记该结点处是否构成单词    
  10.     char trans[11];               // 翻译  
  11. }TrieNode , *Trie;  
  12.   
  13. TrieNode* createTrieNode()  
  14. {  
  15.     TrieNode* node = (TrieNode *)malloc(sizeof(TrieNode));  
  16.     node->count = 0;  
  17.     node->exist = false;  
  18.     memset(node->next , 0 , sizeof(node->next));    // 初始化为空指针  
  19.     return node;  
  20. }  
  21.   
  22. void Trie_insert(Trie root, char* word , char* trans)  
  23. {  
  24.     Trie node = root;  
  25.     char *p = word;  
  26.     int id;  
  27.     while( *p )  
  28.     {  
  29.         id = *p - 'a';  
  30.         if(node->next[id] == NULL)  
  31.         {  
  32.             node->next[id] = createTrieNode();  
  33.         }  
  34.         node = node->next[id];  // 每插入一步,相当于有一个新串经过,指针向下移动  
  35.         ++p;  
  36.         node->count += 1;      // 这行代码用于统计每个单词前缀出现的次数(也包括统计每个单词出现的次数)  
  37.     }  
  38.     node->exist = true;        // 单词结束的地方标记此处可以构成一个单词  
  39.     strcpy(node->trans , trans);  
  40. }  
  41.   
  42. char* Trie_search(Trie root, char* word)  
  43. {  
  44.     Trie node = root;  
  45.     char *p = word;  
  46.     int id;  
  47.     while( *p )  
  48.     {  
  49.         id = *p - 'a';  
  50.         node = node->next[id];  
  51.         ++p;  
  52.         if(node == NULL)  
  53.             return 0;  
  54.     }  
  55.     if(node->exist)          // 查找成功  
  56.         return node->trans;  
  57.     else                     // 查找失败  
  58.         return NULL;  
  59. }  
  60.   
  61. int main(void)  
  62. {  
  63.     Trie root = createTrieNode();     // 初始化字典树的根节点  
  64.     char str1[3003] , str2[3003] , str[3003] , *p;  
  65.     int i , k;  
  66.   
  67.     scanf("%s",str1);  
  68.     while(scanf("%s",str1) && strcmp(str1 , "END") != 0)  
  69.     {  
  70.         scanf("%s",str2);  
  71.         Trie_insert(root , str2 , str1);  
  72.     }  
  73.   
  74.     getchar();  
  75.     gets(str1);  
  76.     k = 0;  
  77.     while(gets(str1))  
  78.     {  
  79.         if(strcmp(str1 , "END") == 0)  
  80.             break;  
  81.         for(i = 0 ; str1[i] != '\0' ; ++i)  
  82.         {  
  83.             if(str1[i] >= 'a' && str1[i] <= 'z')  
  84.             {  
  85.                 str[k++] = str1[i];  
  86.             }  
  87.             else  
  88.             {  
  89.                 str[k] = '\0';  
  90.                 p = Trie_search(root , str);  
  91.                 if(p)  
  92.                     printf("%s", p);  
  93.                 else  
  94.                     printf("%s", str);  
  95.                 k = 0;  
  96.                 printf("%c", str1[i]);  
  97.             }  
  98.         }  
  99.         printf("\n");  
  100.     }  
  101.   
  102.     return 0;  
  103. }  
 
深入理解Trie树:高效文本搜索的实现与优化 Trie树(又称前缀树)是一种用于高效检索的树形数据结构,主要用于字符串处理。它常用于实现动态字典、自动补全、词频统计等功能。Trie树的主要特点是共享相同前缀的字符串路径,从而降低了空间复杂度和查询时间。在本篇文章中,我们将深入探讨Trie树的实现细节,并展示其在文本搜索中的应用,包括代码实例和详细解析。 阅读详情

相关推荐

【Unity】YooAsset + HybridCLR热更

本文介绍了Unity游戏开发中的热更新技术方案,重点对比了资源热更和代码热更的主流工具。资源热更方面分析了Asset Bundle、Addressable和YooAsset的优缺点;代码热更部分比较了xLua、ILRuntime、InjectFix和HybridCLR的性能特点。文章详细演示了HybridCLR+YooAsset的实践方案,包括加载热更新程序集、反射调用热更代码、资源打包部署等完整流程。该方案通过原生运行时实现程序集级热更,具有极低性能损耗和全生命周期适用性,配合YooAsset简化了资源管

欢迎指错||纠正||建议||水评+点赞&&评论&&关注&&转发 2916

Trie树(字典树&前缀树)

Trie树的学习 文章目录1. Trie树的基本知识2. Trie树的实现2.1 二维数组实现2.2 TrieNode实现2.3 方法对比2.4 问题分析 1. Trie树的基本知识 Trie 树(又叫「前缀树」或「字典树」)是一种用于快速查询「某个字符串/字符前缀」是否存在的数据结构。 Trie 树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起。 Trie树是一种哈希树的变种。 典型应用:用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。 优点:最大限度

小朱小朱绝不认输的博客 2540

傅里叶变换实战:用Python手把手教你分析音频频谱(附完整代码)

本文详细介绍了如何使用Python实现傅里叶变换进行音频频谱分析,涵盖音频加载、预处理、FFT应用、频谱可视化及特征提取等关键步骤。通过实战案例和完整代码,帮助开发者掌握数字信号处理技术,特别适合音频分析和音乐信息检索领域的应用。

weixin_29090677的博客 150

TrieNode 字典树

这个题目可以很好地看做字典树在字符串搜索中的运用,在查找的过程中,为了匹配‘.’能够代替任一字符,结合使用了BFSstruct TrieNode2 { TrieNode2* next[26]; bool isFinished; //用于标记,string的结束 TrieNode2() { isFinished = false; for

拥抱新生活 1267

YOLOv5+Astra Pro Plus深度相机:手把手教你实现实时目标检测(附ROS配置技巧)

本文详细介绍了如何将YOLOv5目标检测算法与Astra Pro Plus深度相机结合,构建实时目标检测系统。内容涵盖从硬件选型、环境搭建、ROS配置到YOLOv5部署与深度信息融合的完整流程,并分享了性能优化、避障算法等实战技巧,帮助开发者在机器人视觉等场景中快速实现智能感知应用。

weixin_28292761的博客 389

MediaCodec视频解码流程详解及参考demo

本文主要讲解mediacodec的视频解码流程,及demo参考例子。

分享音视频技术 7875

Trie树的详解及应用

Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。   Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度

鱼思故渊的专栏 1780

AI视频生成模型训练:从数据准备到部署全流程

咱们这次的目的就像是要建造一座超级厉害的AI视频生成魔法城堡。范围呢,就是从收集建造城堡的材料(数据准备)开始,到把城堡搭建起来(模型训练),再到让城堡能供大家使用(模型部署),涵盖了整个过程的方方面面。接下来我们会先给大家介绍一些重要的术语,就像认识冒险途中的关键道具一样。然后引入一个有趣的故事,带大家进入AI视频生成的奇妙世界,解释核心概念以及它们之间的关系。接着会详细讲解核心算法原理、数学模型和公式,还会有项目实战,带大家亲自体验代码的魅力。

小白菜的博客 399

【算法入门】Trie 树详解及其应用

1. 什么是trie树   1.Trie树 (特例结构树)         Trie树,又称单词查找树、字典树,是一种树形结构,是一种哈希树的变种,是一种用于快速检索的多叉树结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。      Trie的核心思想是空间换时间。

summer 2471

【YOLOv8改进-损失函数】 YOLOv8自带损失函数CIoU / DIoU / GIoU 详解,以及如何切换损失函数

在介绍YOLOv8中的各种Iou之前,我们先来了解了解目标检测中的损失函数。目标检测模型通常包含两类损失函数:类别损失(分类)和位置损失(回归)。这两类损失函数用于检测模型的最后一部分,根据模型的输出(类别和位置)以及实际标注框(类别和位置),分别计算类别损失和位置损失。

专注于图像领域,主要研究内容包括计算机视觉和深度学习,特别是在图像分类、目标检测和图像生成等方面有深入的研究和实践经验。 5190

AI视频生成工作流开发:从产品拆解到带货视频全流程实现

本文介绍了一套完整的AI视频生成工作流开发方案,该系统能够自动分析产品素材并生成9个标准化分镜,支持白底图智能替换,最终输出专业带货视频。系统基于Coze平台搭建,整合豆包大模型及多模态生成模型,实现从产品分析、脚本生成到视频合成的全流程自动化。关键模块包括产品特征提取、智能分镜分配、视觉内容生成和用户交互调整,支持手动优化提示词和输出结果。该方案采用模块化设计,结合多种AI模型协同工作,为电商视频制作提供高效解决方案。

huanghm88的专栏 384

Hash树Trie树详解及其应用

一、知识简介         最近在看字符串算法了,其中字典树、AC自动机和后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用。       字典树(Trie)可以保存一些字符串->值的对应关系。基本上,它跟 Java 的 HashMap 功能相同,都是 key-value 映射,只不过 Trie 的 key 只能是字符串。   Trie 的强大之处就在于它的时间复杂度。它的插入和

samjustin的博客 3608

【Halcon 】Halcon 实战:如何为 XLD 模板添加极性信息以提升匹配精度?

本文介绍了Halcon中为XLD轮廓模板添加极性信息的实用技巧。通过先以"ignore_local_polarity"模式创建模板,首次匹配获得位置后,使用set_shape_model_metric运算符结合真实图像为模板注入极性信息(边缘明暗方向)。这种方法弥补了XLD模板初始不支持极性的缺陷,可显著提升后续匹配的精度和抗干扰能力。实验显示,添加极性信息后的模板匹配效果明显优于无极性的匹配结果。该技术特别适用于基于手动绘制或处理后的XLD轮廓创建模板的场景。

songhuangong123的博客 751
上一篇: C++ - CppUnit 使用简介
下一篇: Linux - 进程(一) 进程空间
张哲BJUT
博客等级 码龄15年 151粉丝 111原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值