数据结构与算法之美学习笔记:35 | Trie树:如何实现搜索引擎的搜索关键词提示功能?

news2024/11/14 2:56:09

目录

  • 前言
  • 什么是“Trie 树”?
  • 如何实现一棵 Trie 树?
  • Trie 树真的很耗内存吗?
  • Trie 树与散列表、红黑树的比较
  • 解答开篇
  • 内容小结

前言

在这里插入图片描述
本节课程思维导图:
在这里插入图片描述
搜索引擎的搜索关键词提示功能,我想你应该不陌生吧?为了方便快速输入,当你在搜索引擎的搜索框中,输入要搜索的文字的某一部分的时候,搜索引擎就会自动弹出下拉框,里面是各种关键词提示。你是否思考过,它是怎么实现的呢?它底层使用的是哪种数据结构和算法呢?其底层最基本的原理就是今天要讲的这种数据结构:Trie 树。

什么是“Trie 树”?

Trie 树,也叫“字典树”。顾名思义,它是一个树形结构。它是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。
Trie 树到底长什么样子?
我举个简单的例子来说明一下。我们有 6 个字符串,它们分别是:how,hi,her,hello,so,see。我们希望在里面多次查找某个字符串是否存在。我们就可以先对这 6 个字符串做一下预处理,组织成 Trie 树的结构,之后每次查找,都是在 Trie 树中进行匹配查找。Trie 树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起。
在这里插入图片描述
其中,根节点不包含任何信息。每个节点表示一个字符串中的字符,从根节点到红色节点的一条路径表示一个字符串(注意:红色节点并不都是叶子节点)。
我画了一个 Trie 树构造的分解过程。构造过程的每一步,都相当于往 Trie 树中插入一个字符串。当所有字符串都插入完成之后,Trie 树就构造好了。
在这里插入图片描述
当我们在 Trie 树中查找一个字符串的时候,比如查找字符串“her”,那我们将要查找的字符串分割成单个的字符 h,e,r,然后从 Trie 树的根节点开始匹配。如图所示,绿色的路径就是在 Trie 树中匹配的路径。
在这里插入图片描述

如何实现一棵 Trie 树?

Trie 树主要有两个操作,一个是将字符串集合构造成 Trie 树。这个过程分解开来的话,就是一个将字符串插入到 Trie 树的过程。另一个是在 Trie 树中查询一个字符串。
如何存储一个 Trie 树?
我先介绍其中一种存储方式,也是经典的存储方式,我们通过一个下标与字符一一映射的数组,来存储子节点的指针。
在这里插入图片描述
假设我们的字符串中只有从 a 到 z 这 26 个小写字母,我们在数组中下标为 0 的位置,存储指向子节点 a 的指针,下标为 1 的位置存储指向子节点 b 的指针,以此类推,下标为 25 的位置,存储的是指向的子节点 z 的指针。如果某个字符的子节点不存在,我们就在对应的下标的位置存储 null。
当我们在 Trie 树中查找字符串的时候,我们就可以通过字符的 ASCII 码减去“a”的 ASCII 码,迅速找到匹配的子节点的指针。比如,d 的 ASCII 码减去 a 的 ASCII 码就是 3,那子节点 d 的指针就存储在数组中下标为 3 的位置中。

public class Trie {
  private TrieNode root = new TrieNode('/'); // 存储无意义字符

  // 往Trie树中插入一个字符串
  public void insert(char[] text) {
    TrieNode p = root;
    for (int i = 0; i < text.length; ++i) {
      int index = text[i] - 'a';
      if (p.children[index] == null) {
        TrieNode newNode = new TrieNode(text[i]);
        p.children[index] = newNode;
      }
      p = p.children[index];
    }
    p.isEndingChar = true;
  }

  // 在Trie树中查找一个字符串
  public boolean find(char[] pattern) {
    TrieNode p = root;
    for (int i = 0; i < pattern.length; ++i) {
      int index = pattern[i] - 'a';
      if (p.children[index] == null) {
        return false; // 不存在pattern
      }
      p = p.children[index];
    }
    if (p.isEndingChar == false) return false; // 不能完全匹配,只是前缀
    else return true; // 找到pattern
  }

  public class TrieNode {
    public char data;
    public TrieNode[] children = new TrieNode[26];
    public boolean isEndingChar = false;
    public TrieNode(char data) {
      this.data = data;
    }
  }
}

现在,我们来看下,在 Trie 树中,查找某个字符串的时间复杂度是多少?
如果要在一组字符串中,频繁地查询某些字符串,用 Trie 树会非常高效。构建 Trie 树的过程,需要扫描所有的字符串,时间复杂度是 O(n)(n 表示所有字符串的长度和)。每次查询时,如果要查询的字符串长度是 k,那我们只需要比对大约 k 个节点,就能完成查询操作。跟原本那组字符串的长度和个数没有任何关系。所以说,构建好 Trie 树后,在其中查找字符串的时间复杂度是 O(k),k 表示要查找的字符串的长度。

Trie 树真的很耗内存吗?

关于 Trie 树,你有没有听过这样一种说法:“Trie 树是非常耗内存的,用的是一种空间换时间的思路”。这是什么原因呢?
刚刚我们在讲 Trie 树的实现的时候,讲到用数组来存储一个节点的子节点的指针。如果字符串中包含从 a 到 z 这 26 个字符,那每个节点都要存储一个长度为 26 的数组,并且每个数组元素要存储一个 8 字节指针(或者是 4 字节,这个大小跟 CPU、操作系统、编译器等有关)。而且,即便一个节点只有很少的子节点,远小于 26 个,比如 3、4 个,我们也要维护一个长度为 26 的数组。
如果字符串中不仅包含小写字母,还包含大写字母、数字、甚至是中文,那需要的存储空间就更多了。
那为了解决这个内存问题,我们是否有其他办法呢?
我们可以稍微牺牲一点查询的效率,将每个节点中的数组换成其他数据结构,来存储一个节点的子节点指针。用哪种数据结构呢?我们的选择其实有很多,比如有序数组、跳表、散列表、红黑树等。
假设我们用有序数组,数组中的指针按照所指向的子节点中的字符的大小顺序排列。查询的时候,我们可以通过二分查找的方法,快速查找到某个字符应该匹配的子节点的指针。但是,在往 Trie 树中插入一个字符串的时候,我们为了维护数组中数据的有序性,就会稍微慢了点。
实际上,Trie 树的变体有很多,都可以在一定程度上解决内存消耗的问题。比如,缩点优化,就是对只有一个子节点的节点,而且此节点不是一个串的结束节点,可以将此节点与子节点合并。这样可以节省空间,但却增加了编码难度。
在这里插入图片描述

Trie 树与散列表、红黑树的比较

我们选了两种数据结构,散列表和红黑树,跟 Trie 树比较一下,看看它们各自的优缺点和应用场景。
在刚刚讲的这个场景,在一组字符串中查找字符串,Trie 树实际上表现得并不好。它对要处理的字符串有极其严苛的要求。
第一,字符串中包含的字符集不能太大。我们前面讲到,如果字符集太大,那存储空间可能就会浪费很多。即便可以优化,但也要付出牺牲查询、插入效率的代价。
第二,要求字符串的前缀重合比较多,不然空间消耗会变大很多。
第三,如果要用 Trie 树解决问题,那我们就要自己从零开始实现一个 Trie 树,还要保证没有 bug,这个在工程上是将简单问题复杂化,除非必须,一般不建议这样做。
第四,我们知道,通过指针串起来的数据块是不连续的,而 Trie 树中用到了指针,所以,对缓存并不友好,性能上会打个折扣。

综合这几点,针对在一组字符串中查找字符串的问题,我们在工程中,更倾向于用散列表或者红黑树。实际上,Trie 树只是不适合精确匹配查找,这种问题更适合用散列表或者红黑树来解决。Trie 树比较适合的是查找前缀匹配的字符串,也就是类似开篇问题的那种场景。

解答开篇

如何利用 Trie 树,实现搜索关键词的提示功能?
我们假设关键词库由用户的热门搜索关键词组成。我们将这个词库构建成一个 Trie 树。当用户输入其中某个单词的时候,把这个词作为一个前缀子串在 Trie 树中匹配。为了讲解方便,我们假设词库里只有 hello、her、hi、how、so、see 这 6 个关键词。当用户输入了字母 h 的时候,我们就把以 h 为前缀的 hello、her、hi、how 展示在搜索提示框内。当用户继续键入字母 e 的时候,我们就把以 he 为前缀的 hello、her 展示在搜索提示框内。这就是搜索关键词提示的最基本的算法原理。
在这里插入图片描述
实际上,搜索引擎的搜索关键词提示功能远非我讲的这么简单。如果再稍微深入一点,你就会想到,上面的解决办法遇到下面几个问题:
我刚讲的思路是针对英文的搜索关键词提示,对于更加复杂的中文来说,词库中的数据又该如何构建成 Trie 树呢?如果词库中有很多关键词,在搜索提示的时候,用户输入关键词,作为前缀在 Trie 树中可以匹配的关键词也有很多,如何选择展示哪些内容呢?像 Google 这样的搜索引擎,用户单词拼写错误的情况下,Google 还是可以使用正确的拼写来做关键词提示,这个又是怎么做到的呢?
实际上,Trie 树的这个应用可以扩展到更加广泛的一个应用上,就是自动输入补全,比如输入法自动补全功能、IDE 代码编辑器自动补全功能、浏览器网址输入的自动补全功能等等。

内容小结

今天我们讲了一种特殊的树,Trie 树。Trie 树是一种解决字符串快速匹配问题的数据结构。如果用来构建 Trie 树的这一组字符串中,前缀重复的情况不是很多,那 Trie 树这种数据结构总体上来讲是比较费内存的,是一种空间换时间的解决问题思路。
尽管比较耗费内存,但是对内存不敏感或者内存消耗在接受范围内的情况下,在 Trie 树中做字符串匹配还是非常高效的,时间复杂度是 O(k),k 表示要匹配的字符串的长度。
但是,Trie 树的优势并不在于,用它来做动态集合数据的查找,因为,这个工作完全可以用更加合适的散列表或者红黑树来替代。Trie 树最有优势的是查找前缀匹配的字符串,比如搜索引擎中的关键词提示功能这个场景,就比较适合用它来解决,也是 Trie 树比较经典的应用场景。

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

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

相关文章

消息可靠性保证

回顾RabbitMQ的消息传递过程 如图所示&#xff0c;发生消息丢失的可能阶段也就是生产者发送消息&#xff0c;时rabbitmq存储消息时&#xff0c;消费者消费消息时。项目源码&#xff1a;gitee 生产者发送消息阶段 生产者发送消息时把交换机名写错生产者发送消息时把routingK…

行为树保姆级教程(以机器人的任务规划为例

行为树 目录 什么是行为树(behavior tree)&#xff1f;行为树的相关术语 行为节点和控制节点不同类型的控制结点&#xff1a; 顺序节点选择节点并行节点装饰结点 机器人的例子&#xff1a;物体搜索 1&#xff1a;如果只存在一个地点A&#xff0c;那么行为树很简单&#xff0…

实验:BGP配置

1.实验目的&#xff1a; 本实验旨在掌握BGP协议的基本概念和配置方法&#xff0c;以及使用Packet Tracer模拟网络环境进行BGP配置的方法。 2.实验要求&#xff1a; 理解BGP协议的基本概念和原理&#xff1b;掌握BGP协议的配置方法&#xff1b;能够使用Packet Tracer模拟网络…

MyBatisPlus使用时报错Invalid value type for attribute ‘factoryBeanObjectType‘

目录 问题 探索过程 总结 问题 今天在学习MyBatisPlus过程中突然报了个错&#xff0c;信息如下 Invalid value type for attribute factoryBeanObjectType: java.lang.String Caused by: java.lang.IllegalArgumentException: Invalid value type for attribute factoryB…

json Deserialization of Python Objects

openweathermap.json {"coord": {"lon": 114.0683, "lat":22.5455},"weather":[ {"id": 803, "main":"Clouds", "description":"多云", "icon":"04d"}],"…

MacOS多屏状态栏位置不固定,程序坞不小心跑到副屏

目录 方式一&#xff1a;通过系统设置方式二&#xff1a;鼠标切换 MacOS多屏状态栏位置不固定&#xff0c;程序坞不小心跑到副屏 方式一&#xff1a;通过系统设置 先切换到左边 再切换到底部 就能回到主屏了 方式二&#xff1a;鼠标切换 我的两个屏幕放置位置如下 鼠标在…

R语言【rgbif】——什么是多值传参?如何在rgbif中一次性传递多个值?多值传参时的要求有哪些?

rgbif版本&#xff1a;3.7.8.1 什么是多值传参&#xff1f; 您是否在使用rgbif时设想过&#xff0c;给某个参数一次性传递许多个值&#xff0c;它将根据这些值独立地进行请求&#xff0c;各自返回独立的结果。 rgbif支持这种工作模式&#xff0c;但是具体的细节需要进一步地…

蓝牙物联网智慧物业解决方案

蓝牙物联网智慧物业解决方案是一种利用蓝牙技术来提高物业管理和服务效率的解决方案。它通过将蓝牙技术与其他智能设备、应用程序和云服务相结合&#xff0c;为物业管理和服务提供更便捷、高效和智能化的支持。 蓝牙物联网智慧物业解决方案包括&#xff1a; 1、设备管理&#…

Crypto基础之密码学

FLAG&#xff1a;20岁的年纪不该困在爱与不爱里&#xff0c;对吗 专研方向: 密码学&#xff0c;Crypto 每日emo&#xff1a;今年你失去了什么&#xff1f; Crypto基础之密码学 前言一、编码Base编码base64&#xff1a;Base32 和 Base16&#xff1a;uuencode&#xff1a;xxencod…

计算机网络——网络层——OSPF协议的介绍

什么是 OSPF &#xff1f; OSPF 是一个基于链路状态的自治系统内部路由协议&#xff0c;在 TCP/IP 的网络层中进行路由选择&#xff0c;常用于构建大型企业网络或者服务上的骨干网络。在互联网核心路由器之间也可以使用。 OSPF 概述 OSPF 使用的是 Dijkstra&#xff08;最短…

智能优化算法应用:基于黏菌算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于黏菌算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于黏菌算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.黏菌算法4.实验参数设定5.算法结果6.参考文献7.MA…

记录 | mac打开终端时报错:login: /opt/homebrew/bin/zsh: No such file or directory [进程已完成]

mac打开终端时报错&#xff1a;login: /opt/homebrew/bin/zsh: No such file or directory [进程已完成]&#xff0c;导致终端没有办法使用的情况 说明 zsh 没有安装或者是安装路径不对 可以看看 /bin 下有没有 zsh&#xff0c;若没有&#xff0c;肯定是有 bash 那就把终端默…

QT- QT-lximagerEidtor图片编辑器

QT- QT-lximagerEidtor图片编辑器 一、演示效果二、关键程序三、下载链接 功能如下&#xff1a; 1、缩放、旋转、翻转和调整图像大小 2、幻灯片 3、缩略图栏&#xff08;左、上或下&#xff09;&#xff1b;不同的缩略图大小 4、Exif数据栏 5、内联图像重命名 6、自定义快捷方式…

JS加密/解密之JSX解密解析(photoshop插件)

简介 Adobe Photoshop 插件通常使用 JSX&#xff08;JavaScript XML&#xff09;脚本语言。这是一种基于JavaScript的扩展&#xff0c;专门设计用于处理Adobe Creative Suite&#xff08;包括Photoshop&#xff09;的任务。JSX脚本允许开发者编写自定义脚本以扩展和增强Photos…

【Eureka】自定义元数据消失原因?

【Eureka】自定义元数据运行很长一段时间后&#xff0c;自定义元数据&#xff08;scheduler.server.enabled&#xff09;偶尔会消失&#xff0c;但服务元数据信息还在 eureka是单节点的&#xff0c;这个应用服务也是单节点的 代码实现方式如下 我看过eureka服务的日志信息&…

在做题中学习(33):只出现一次的数字 II

137. 只出现一次的数字 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 1.首先想到出现三次的数&#xff0c;它们仨的任意一位都是相同的&#xff08;1/0&#xff09; 2.可以发现出现三次的数的某一位和a某一位在所有情况下%3最后的结果都和a的那一位相同&…

06.迪米特法则(Demeter Principle)

明 嘉靖四十年 江南织造总局 小黄门唯唯诺诺的听完了镇守太监杨金水的训斥&#xff0c;赶忙回答&#xff1a;“知道了&#xff0c;干爹&#xff01;” “知道什么&#xff1f;&#xff01;&#xff01;” 杨金水打断了他的话&#xff0c;眼神突然变得凌厉起来&#xff1a; “有…

企业计算机服务器中了halo勒索病毒如何解密,halo勒索病毒恢复流程

网络技术的不断发展与应用&#xff0c;为企业的生产运营提供了极大便利&#xff0c;越来越多的企业使用数据库存储企业的重要数据&#xff0c;方便工作与生产&#xff0c;但网络是一把双刃剑&#xff0c;网络安全威胁一直存在&#xff0c;并且网络威胁的手段也在不断升级。在本…

我的隐私计算学习——匿踪查询

笔记内容来自多本书籍、学术资料、白皮书及ChatGPT等工具&#xff0c;经由自己阅读后整理而成。 &#xff08;一&#xff09;PIR的介绍 ​ 匿踪查询&#xff0c;即隐私信息检索&#xff08;Private InformationRetrieval&#xff0c;PIR&#xff09;&#xff0c;是安全多方计算…

C# OpenVINO 直接读取百度模型实现印章检测

目录 效果 模型信息 项目 代码 下载 其他 C# OpenVINO 直接读取百度模型实现印章检测 效果 模型信息 Inputs ------------------------- name&#xff1a;scale_factor tensor&#xff1a;F32[?, 2] name&#xff1a;image tensor&#xff1a;F32[?, 3, 608, 608] …