0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

数据结构字典树的实现

算法与数据结构 来源:bigsai 作者:bigsai 2021-09-07 15:03 次阅读

什么是字典树字典树,是一种空间换时间的数据结构,又称Trie树、前缀树,是一种树形结构(字典树是一种数据结构),典型用于统计、排序、和保存大量字符串。所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

可能大部分情况你很难直观或者有接触的体验,可能对前缀这个玩意没啥概念,可能做题遇到前缀问题也是暴力匹配蒙混过关,如果字符串比较少使用哈希表等结构可能也能蒙混过关,但如果字符串比较长、相同前缀较多那么使用字典树可以大大减少内存的使用和效率。一个字典树的应用场景:在搜索框输入部分单词下面会有一些神关联的搜索内容,你有时候都很神奇是怎么做到的,这其实就是字典树的一个思想。

对于字典树,有三个重要性质:

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

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

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

一个字典树

设计实现字典树上面已经介绍了什么是字典树,那么我们开始设计一个字典树吧!

对于字典树,可能不同的场景或者需求设计上有一些细致的区别,但整体来说一般的字典树有插入、查询(指定字符串)、查询(前缀)。

我们首先来分析一下简单情况吧,就是字符串中全部是26个小写字母,刚好力扣208实现Trie树可以作为一个实现的模板。

实现 Trie 类:

Trie() 初始化前缀树对象。

void insert(String word) 向前缀树中插入字符串 word 。

boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。

boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。怎么设计这个字典树呢?

对于一个字典树Trie类,肯定是要有一个根节点root的,而这个节点类型TrieNode也有很多设计方式,在这里我们为了简单放一个26个大小的TrieNode类型数组,分别对应‘a’-‘z’的字符,同时用一个boolean类型变量isEnd表示是否为字符串末尾结束(如果为true说明)。

class TrieNode {

TrieNode son[];

boolean isEnd;//结束标志

public TrieNode()//初始化

{

son=new TrieNode[26];

}

}

用数组的话如果字符比较多的话可能会消耗一些内存空间,但是这里26个连续字符还好的,如果向一个字典树中添加big,bit,bz 那么它其实是这样的:

那么再分析一下具体操作:

插入操作:遍历字符串,同时从字典树root节点开始遍历,找到每个字符对应的位置首先判断是否为空,如果为空需要创建一个新的Trie。比如插入big的枚举第一个b时候创建TrieNode,后面也是同理。不过重要的是要在停止的那个TrieNode将isEnd设为true表明这个节点是构成字符串的末尾节点。

这部分对应的关键代码为:

TrieNode root;

/** 初始化 */

public Trie() {

root=new TrieNode();

}

/** Inserts a word into the trie. */

public void insert(String word) {

TrieNode node=root;//临时节点用来枚举

for(int i=0;i《word.length();i++)//枚举字符串

{

int index=word.charAt(i)-‘a’;//找到26个对应位置

if(node.son[index]==null)//如果为空需要创建

{

node.son[index]=new TrieNode();

}

node=node.son[index];

}

node.isEnd=true;//最后一个节点

}

查询操作:查询是建立在字典树已经建好的情况下,这个过程和查询有些类似但不需要创建TrieNode,如果枚举的过程一旦发现该TrieNode未被初始化(即为空)则返回false,如果顺利到最后看看该节点的isEnd是否为true(是否已插入已改字符结尾的字符串),如果为true则返回true。

这里用一个例子可能更好懂。插入big串,如果查找ba会因为第二次a对应TrieNode为null为为空。如果查找bi也会返回失败,因为之前插入的big只在g字符对应TrieNode标识isEnd=true,但i字符下面的isEnd为false,即不存在bi字符串。

该部分对应的核心代码为:

public boolean search(String word) {

TrieNode node=root;

for(int i=0;i《word.length();i++)

{

int index=word.charAt(i)-‘a’;

if(node.son[index]==null)//为null直接返回false

{

return false;

}

node=node.son[index];

}

return node.isEnd==true;

}

前缀查找:和查询很相似但是有点区别,查找失败的话返回false,但是如果能进行到最后一步那么返回true。上面例子插入big查找bi同样返回true,因为存在以它为前缀的字符串。

该对应对应的核心代码为:

public boolean startsWith(String prefix) {

TrieNode node=root;

for(int i=0;i《prefix.length();i++)

{

int index=prefix.charAt(i)-‘a’;

if(node.son[index]==null)

{

return false;

}

node=node.son[index];

}

//能执行到最后即返回true

return true;

}

上面代码合在一起就是完整的字典树了,最基础的版本。完整版为:

22af1a8a-0f8c-11ec-8fb8-12bb97331649.png

代码

字典树小思考字典树基础班很容易,但很可能会出现一些延伸。

对于上面是26个字符的,我们很容易用ASCII找到对应索引,如果字符可能性比较多,用数组可能浪费的空间比较大,那我们也可以用HashMap或者List来存储元素啊,用List的话就需要顺序枚举,用HashMap就可以直接查询,这里就讲解一个使用HashMap()实现的字典树。

使用HashMap替代数组(不过使用哈希就不自带排序功能了),其实逻辑是一样的,只需要判断时候用HashMap判断是否存在对应的key即可,HashMap的类型为:

Map《Character,TrieNode》 sonMap;

使用HashMap实现的字典树完整代码为:

import java.util.HashMap;

import java.util.Map;

public class Trie{

class TrieNode{

Map《Character,TrieNode》 sonMap;

boolean idEnd;

public TrieNode()

{

sonMap=new HashMap《》();

}

}

TrieNode root;

public Trie()

{

root=new TrieNode();

}

public void insert(String word) {

TrieNode node=root;

for(int i=0;i《word.length();i++)

{

char ch=word.charAt(i);

if(!node.sonMap.containsKey(ch))//不存在插入

{

node.sonMap.put(ch,new TrieNode());

}

node=node.sonMap.get(ch);

}

node.idEnd=true;

}

public boolean search(String word) {

TrieNode node=root;

for(int i=0;i《word.length();i++)

{

char ch=word.charAt(i);

if(!node.sonMap.containsKey(ch))

{

return false;

}

node=node.sonMap.get(ch);

}

return node.idEnd==true;//必须标记为true证明有该字符串

}

public boolean startsWith(String prefix) {

TrieNode node=root;

for(int i=0;i《prefix.length();i++)

{

char ch=prefix.charAt(i);

if(!node.sonMap.containsKey(ch))

{

return false;

}

node=node.sonMap.get(ch);

}

return true;//执行到最后一步即可

}

}

前面讲了,字典树用于大量字符的统计、排序、储存,其实排序就是和采用数组的方式可以进行排序,因为字符的ASCII有序,在读取时候可以按照这个规则读取,这个思想就和基数排序有点像了。

而统计的话可能会面临数量上统计,可能是出现过次数或者前缀单词数量统计,如果每次都枚举可能有点浪费时间,但你可以TrieNode中添加一个变量,每次插入的时候可以统计次数。如果字符串有重复那可以直接添加,如果字符串要去重那可以确定插入成功再给路径上前缀单词总数分别自增。这个的话就要具体问题具体分析了。

此外,字典树还有一个在ACM中用于解决求异或最值的问题,我们称之为:01字典树,大家感兴趣也可以自行了解(后面可能会介绍)。

总结通过本文,想必你对字典树有了一个较好的认识,本篇的话目的还是在于让读者能够认识和学会基础的字典树,对其它变形优化能有个初步的认识。

字典树可以最大限度地减少无谓的字符串比较,用于词频统计和大量字符串排序。自带排序功能,使用中序遍历序列即可得到排序序列。但是如果字符很多相同前缀很少的话那字典树就没啥效率优势的(因为要一个一个访问节点)。

字典树的真实应用有很多,例如字符串检索、文本预测、自动完成,see also,拼写检查、词频统计、排序、字符串最长公共前缀、字符串搜索的前缀匹配、作为其他数据结构和算法的辅助结构等等,这里就不再介绍啦。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6867

    浏览量

    88799
  • 内存
    +关注

    关注

    8

    文章

    2996

    浏览量

    73867
  • 字符串
    +关注

    关注

    1

    文章

    577

    浏览量

    20485

原文标题:字典树,不就有点不一样的一颗树

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    DDC264配置寄存器数据写入和320 DCLK时钟脉冲后的回读数据结构是什么?

    配置寄存器数据写入和320 DCLK时钟脉冲后的回读数据结构是什么? 根据注和表9,16位配置寄存器数据,4位修订ID, 300位校验模式,怎么可能有1024 TOTAL READBACK BITS, format = 0
    发表于 11-19 07:58

    视觉软件HALCON的数据结构

    在研究机器视觉算法之前,我们需要先了解机器视觉应用中涉及的基本数据结构。Halcon数据结构主要有图像参数和控制参数两类参数。图像参数包括:image、region、XLD,控制参数包括:string、integer、real、handle、tuple数组等。
    的头像 发表于 11-14 10:20 140次阅读
    视觉软件HALCON的<b class='flag-5'>数据结构</b>

    嵌入式常用数据结构有哪些

    在嵌入式编程中,数据结构的选择和使用对于程序的性能、内存管理以及开发效率都具有重要影响。嵌入式系统由于资源受限(如处理器速度、内存大小等),因此对数据结构的选择和使用尤为关键。以下是嵌入式编程中常用的几种数据结构,结合具体特点和
    的头像 发表于 09-02 15:25 380次阅读

    揭秘编程核心:基本数据结构与算法思想详解

    描述问题的数据除了各数据元素本身,还要考虑各元素的逻辑关系,主要是一对一的线性关系,一对多的型关系和多对多的图形关系。
    的头像 发表于 04-25 11:51 1033次阅读
    揭秘编程核心:基本<b class='flag-5'>数据结构</b>与算法思想详解

    探索编程世界的七大数据结构

    结构就像是一颗倒挂的小树,有根、有枝、有叶。它是一种非线性的数据结构,以层级的方式存储数据,顶部是根节点,底部是叶节点。
    的头像 发表于 04-16 12:04 354次阅读

    TASKING编译器是否可以将数据结构设置为 \"打包\"?

    TASKING 编译器是否可以将数据结构设置为 \"打包\"? GCC 很早以前就提供了这种可能性,可以将__attribute__((packed))与对齐指令结合使用。 对于
    发表于 03-05 06:00

    矢量与栅格数据结构各有什么特征

    矢量数据结构和栅格数据结构是地理信息系统(GIS)中最常用的两种数据结构。它们在存储和表示地理要素上有着不同的方法和特征。在接下来的文章中,我们将详细介绍这两种数据结构并比较它们的特点
    的头像 发表于 02-25 15:06 2315次阅读

    区块链是什么样的数据结构组织

    区块链是一种特殊的数据结构,它以分布式、去中心化的方式组织和存储数据。区块链的核心原理是将数据分布在网络的各个节点上,通过密码学算法保证数据的安全和可靠性。在区块链上,
    的头像 发表于 01-11 10:57 1949次阅读

    C语言数据结构之跳表详解

    大家好,今天分享一篇C语言数据结构相关的文章--跳表。
    的头像 发表于 12-29 09:32 791次阅读
    C语言<b class='flag-5'>数据结构</b>之跳表详解

    时钟是什么?介绍两种时钟树结构

    今天来聊一聊时钟。首先我先讲一下我所理解的时钟是什么,然后介绍两种时钟树结构
    的头像 发表于 12-06 15:23 1595次阅读

    redis数据结构的底层实现

    Redis是一种内存键值数据库,常用于缓存、消息队列、实时数据分析等场景。它的高性能得益于其精心设计的数据结构和底层实现。本文将详细介绍Redis常用的
    的头像 发表于 12-05 10:14 587次阅读

    redis hash底层实现原理

    数据结构是如何实现的呢?本文将详细介绍Redis哈希底层的实现原理。 在Redis中,每个哈希都是由一个类似于字典(Dictionary)的结构
    的头像 发表于 12-04 16:27 557次阅读

    不同数据结构的定义代码

    数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
    的头像 发表于 11-29 14:13 618次阅读

    【米尔-TIAM62开发板-接替335x-试用评测】+(三)手把手创建Uboot设备与内核设备实战

    ,并据此进行正确的初始化操作。 这就是板载设备层级的基本介绍。具体实现可能会因硬件和固件的不同而有所差异,但基本原理是相同的:设备提供了一种标准化的方式来描述和初始化硬件设备。这种数据结构
    发表于 11-28 09:54

    与二叉的定义

    结构 是一类重要的 非线性数据结构 ,其中以和二叉最为常用,直观来看,是以分支关系定义
    的头像 发表于 11-24 15:57 1269次阅读
    <b class='flag-5'>树</b>与二叉<b class='flag-5'>树</b>的定义