android搜索框功能实现_巧用 Trie 树,实现搜索引擎关键词提示功能

389de5dbf5e36b70fe35943b66e70e2c.gif1de084f0065426d35f509976d58c96c0.png来源 | 码海责编 | Carol封图 | CSDN 付费下载于视觉中国我们几乎每天都在用搜索引擎搜索信息,相信大家肯定有注意过这样一个细节:当输入某个字符的时候,搜索引框底下会出现多个推荐词,如下,输入「python」后,底下会出现挺多以python 为前缀的推荐搜索文本,它是如何实现的呢?
21bdb6e434a0067c98dc6f5ff0cdf755.png
文章标题已经给出答案了,没错,用 Trie 树。本文将会从以下几个方面来简述一下 Trie 树的原理,以让大家对 Trie 树有一个比较全面的认识。
  • 什么是 Trie 树
  • Trie 树的实现
  • 如何实现搜索字符串自动提示
  • 再谈 Trie 树
相信大家看了肯定有收获

07ab667ac26582f0c919d80fb3882099.png

什么是 Trie 树Trie 树,又称前缀树,字典树,或单词查找树,是一种树形结构,也是哈希表的变种,它是一种专门处理字段串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题,主要被搜索引擎用来做文本词频的统计。画重点:快速字符串匹配,词频统计。1、快速字符串匹配假设想要在一串字符串如 a, to, tea, ted, ten, i, in, inn 中多次查找某个字符串是否存在,该怎么做呢,很直观的想法是用 hash,这种确实没问题,如果 hash 函数设计得好的话,如果 hash 函数设计得不好,很容易产生冲突,进而退化成字符串间的比较,另外,在英文中其实有很多单词有共同的前缀,比如中 tea, ted, ten 这三个单词有共同的前缀 te, 如果用 hash 的话,无疑这些共同前缀相当于重复存了多次,比较费空间。如果用 Trie 树的话,能解决以上两个问题,先来看下 trie 树是如何表示的,以以上的一组字符串 a, to, tea, ted, ten, i, in, inn 为例,它们组成的 Trie 树如下:
46d0e82cafb1d2ea232722d1be4bf553.png
如果要查找某个字符串的话,从根节点出发,每次取待查找字符串中的一个字符往下遍历,即可找到,可以看到它的查找时间复杂度为 O(N) (N 为字符串长度),还是很快的(英文单词普遍比较短)。2、词频统计 只要在每个结点上加一个计数器,遍历单词时,所有字符串的最后一个字符对应结点的计算器都加 1, 如以 a,an,and 构造的 Trie 树如下,每个结点计算器都为 1,代表以此结点存储字符为终止字符的单词分别为 1 个。
8affeea36200220ddcaac36e943737fa.png
从前面 Trie 树的图解可以看到 Trie 树的本质就是前缀树,通过提取出字符串的公共前缀(如果有的话),以达到快速匹配字符串的目的。通过前缀匹配,使用 Trie 树查找字符串的效率大大提高!从以上 Trie 树的图解我们可以得出 Trie 树的以下几个特点
  1. 根节点不包含字符,除根节点外每个节点只包含一个字符
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。如上图中从根节点到结点 o,经过的字符为「t」和「o」,所以它表示单词 to。
  3. 每个节点的所有子节点包含的字符都不相同,这一点也就保证了相同的前缀能够得到复用。
那么  Trie 树该怎么表示呢?

9f259e0fea3d00108dca4519997446d3.png

Trie 树的实现从上文我们对 Trie 树的剖析可以很明显地看到 Trie 树是一颗多叉树,那么多叉树该怎么表示呢,假设字符串都是由 26 个小写字母组成,则显然 Trie 树应该是一颗 26 叉树,每个节点包含 26 个子节点,如下
97501f11f9e6d12642a19cef0328548a.png
上图可以看出,26 个子节点我们可以用大小为 26 的数组表示,所以 Trie 树表示如下
/**
 * 26 个字母
 */static final int ALPHABET_SIZE = 26;/**
 * Trie 树的节点表示
 */static class TriedNode {/**
     * 根节点专用,存储 "/"
     */public char val;/**
     * 以此结点字符为终止字符的字符串的个数
     */public int frequency;/**
     * 节点指向的子节点
     */
    TriedNode[] children = new TriedNode[ALPHABET_SIZE];public TriedNode(char val) {this.val = val;
    }
}/**
 * Trie 树
 */static class TrieTree {private TriedNode root = new TriedNode('/'); // 根节点
}
Trie 树的表现有了,现在我们来看下 Trie 树的两个主要操作
  1. 根据一组字符串构造 Trie 树
  2. 在 Trie 树中查找字符串是否存在
先来看如何根据一组字符串构造 Trie 树,首先如何根据一个单词来构造 Trie 树呢,假设我们以单词 「and」 为例来看下 Trie 树的表现形式
80fe1052f3c9eeaa479002772364c8f0.png
注:图中的数字表示数组的元素位置可以看到构建 Trie 树的主要步骤如下
  1. 构建根节点,此时根节点存有一个元素大小为 26 的数组
  2. 遍历字符串「and」
  3. 遍历第一个字符 a 时,将上述数组的第一个元素赋值为一个 TriedNode 实例(假设其名为 A)
  4. 当遍历第二个字符 n 时,将 A 结点 TriedNode 数组下标为 n-a = 13 (a 的 ascii 为 97,n 的 ascii 码为 110) 的元素赋值为一个 TriedNode 实例(假设其名为 N)
  5. 同理,当遍历第三个字符 d 时,将 N 结点 TriedNode 数组的第 4 个元素(下标为 3)赋值为一个 TriedNode 实例(假设其名为 D),同时也将其结点的 frequency 加一,代表以此字符为终止字符的字符串多了一个。
由以上分析不难写出根据字符串构建 Trie 树的代码,如下
/**
 * Trie 树
 */static class TrieTree {private TriedNode root = new TriedNode('/'); // 根节点/**
     * 以 String 为条件构建 Trie 树
     * @param s
     */public void insertString(String s) {
        TriedNode p = root;for (int i = 0; i             char c = s.charAt(i);int index  = c-'a';if (p.children[index] == null) {
                p.children[index] = new TriedNode(c);
            }
            p = p.children[index];//Process char
        }
        p.frequency++;
    }
}
Trie 树构造好了,再在 Trie 树中查找某字符串是否存在就简单很多了,遍历字符串,查看每个字符在相应层级的数组位置的元素是否为空即可,如果是,说明不存在,如果不是,则继续遍历字符查找,直到遍历完成,代码如下
/**
 * 查找字符串是否在原字符串集合中
 * @param s
 * @return boolean
 */public boolean findStr(String s) {
    TriedNode p = root;for (int i = 0; i         // 当前被遍历的字符char c = s.charAt(i);int index  = c-'a';if (p.children[index] == null) {// 如果字符对应位置的数组元素为空,说明肯定不存在此字符,终止之后的字符遍历return false;
        }// 如果存在,则继续往后遍历字符串
        p = p.children[index];
    }return true;
}
由于在节点中也用 frequency 保存了单词数,所以如果在 Trie 树中最终发现字符串存在,也可以随便查找出此字符串的个数。

b10701dc89a5f5901c16db9f95f0d63e.png

如何实现搜索字符串自动提示功能

有了 Trie 树,相信大家不难解决开篇的这个问题,首先搜索引擎根据用户的搜索词构建一颗 Trie 树,假设这个搜索词库是 a, to, tea, ted, ten, i, in, inn,则构建的 Trie 树为:
46d0e82cafb1d2ea232722d1be4bf553.png
那么当用户在搜索框输入「te」的时候,根据 Trie 树的特性得知以  te 为前缀的字符串有 tea,ted,ten,则应该在搜索框提示词中展示这三个字符串。这里有一个小问题,一般搜索框只会展示 10 个搜索词,但以用户输入字符串为前缀的字符串可能远超 10 次,到底该展示哪 10 个呢,最简单的规则是展示搜索次数最多的 10 个字符串,于是问题就转化为了 TopK 问题,维护一个有 10 个元素的小顶堆,步骤如下
  1. 先根据用户输入的前缀在树中找出含有此前缀的所有字符串
  2. 我们知道在节点中保存了字符串的被搜索次数,所以利用小顶堆即可算出被搜索次数最多的 10 个字符串,即可得最终展示给用户的提示词。
注意:这里的求 TopK 要用是小顶堆,不是大顶堆哦,在之前一篇文章中有读者提出了疑问,不要搞混了,小顶堆是求最大的 Top K 值,大顶堆是求最小的 TopK 值,由于我们要求最多的前 10 个搜索词,所以应该是用小顶堆)。

这样就解决了,考虑以下现象:我们在输入搜索词的时候,搜索引擎给出的提示词可能并不是以用户输入的字符串为前缀的。

feecdbedfdda7935245dc891ceda2be6.png

如图示:搜索引擎给出的搜索关键字并不包含有「brekfa」 前缀。

这种又是怎么实现的呢,它实际上用到了字符串编辑距离的思想,所谓字符串编辑距离是说一个字符串可以通过增删改查字符来变成另外一个字符串。

2359308b7c99d3b4f310696663131619.png
如图示: brekfa 添加 a 之后变成了  breakfa显然所作的增删改查次数越少,效率越高,经过最少的字符中编辑变成另一个合法的字符串后,就以此字符串为前缀去 Trie 树中查找提示词。当然了,像 Google 这样的搜索引擎要实时显示这些结果,背后肯定经过了很多改造。不过原理都大同小异。

5947d04b5d28cb12e042f1498b63f134.png

再谈 Trie 树

从前面的介绍中我们可以看到使用 Trie 树确实在能在快速查找字符串与词频统计上发挥重要作用,但天下没有免费的午餐,如果字符集比较大的话,用 Trie 树可能会造成空间的浪费,以上文中构建的 Trie 树为例
80fe1052f3c9eeaa479002772364c8f0.png
每个结点维护一个 26 个元素大小的数组,共有 4 个数组,也就是分配了 26 x 4 = 104  个元素的空间,但实际上只有三个元素空间(a,n,d)被分配了,浪费了 101 个空间,空间利率率很低,所以一般更适用于字符串前缀重复比较多的情况,当然也可以考虑对 Trie 树进行如下缩点优化,能节省一些空间
64e434925c622b08542df59617f70bf1.png
当然这么优化后也增加了代码的编码难度,所以要视情况而定。另外如果用 Trie 树的话,一般需要我们自己编码,对工程师的编码能力要求较高,所以是否用 Trie 树我们一般建议如下:
  1. 如果是字符串的精确匹配查找,我们一般建议使用散列表或红黑树来解决,毕竟很多语言的类库都有现成的,不需要自己实现,拿来即用

  2. 如果需要进行前缀匹配查找,则用 Trie 树更合适一些

13b07b33b974c008ce293ece15e85bf3.png

总结

本文通过搜索引擎字符串提示简要地概述了其实现原理,相信大家应该理解了,需要注意的是其使用场景,更推荐在需要前缀匹配查找的时候用 Trie 树,否则像一般的精确匹配查找等更推荐用散列表和红黑树这些很成熟的数据结构,毕竟这两数据结构实现一般在类库中都是实现了的,不需要自己实现,尽量不要重复造轮子。29ebb0bfa30587d80fa4394ffed974bd.png7af65b1568e52c26c8b111d01e9c1548.png推荐阅读
  • 手把手教你配置VS Code 远程开发工具,工作效率提升N倍

  • 用大白话彻底搞懂HBase RowKey详细设计

  • 后端程序员必备:书写高质量SQL的30条建议

  • Go 远超 Python,机器学习人才极度稀缺,全球 16,655 位程序员告诉你这些真相!

  • 任正非谈“狼文化”:华为没有 996,更没有 007

  • 区块链必读“上链”哲学:“胖链下”与“瘦链上”

  • 在商业中,如何与人工智能建立共生关系?

真香,朕在看了!

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

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

相关文章

qt5.9.0调试如何查看变量的值_深入了解 Java 调试

Bug(俗称"八阿哥") 是软件开发绕不过的一道坎,因此调试便成了每位程序员一项必备的核心技能。调试不仅有助于理解程序的运行流程,还能改进代码质量,最终提高开发者解决问题的能力以及交付软件的品质。本文旨在讨论 Java 调试关键技…

(6) 如何用Apache POI操作Excel文件-----POI-3.10的一个和注解(comment)相关的另外一个bug...

如果POI-3.10往一个工作表(sheet)里面插入数据的话,需要注意了,其有一个不太被容易发现的bug。 被插入的工作表(sheet)里面的单元格没有包含任何的注解(comment)的时候,插…

python导入模块以及类_python模块的导入以及模块简介

标签: 一、模块的定义及类型 1、定义 模块就是用一堆的代码实现了一些功能的代码的集合,通常一个或者多个函数写在一个.py文件里,而如果有些功能实现起来很复杂,那么就需要创建n个.py文件,这n个.py文件的集合就是模块 …

mnist手写数字数据集_mnist手写数据集(1. 加载与可视化)

》》欢迎 点赞,留言,收藏加关注《《1. 模型构建的步骤:在构建AI模型时,一般有以下主要步骤:准备数据、数据预处理、划分数据集、配置模型、训练模型、评估优化、模型应用,如下图所示:【注意】由…

python凯撒密码实现_密码:凯撒密码及其Python实现

python凯撒密码实现Before we start let’s some basic terminology... 在开始之前,让我们先介绍一些基本术语... The art and science to achieve security by encoding messages to make them unreadable are known as Cryptography. That’s what the whole art…

qtextedit 默认文案_QT-纯代码控件-QSplitter(分裂器)

版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/qq_41488943/article/details/96431379使用Qplitter实现页面的三布局分布1.新建一个无ui界面的工程&…

TYVJ P1030 乳草的入侵 Label:跳马问题

背景 USACO OCT09 6TH描述 Farmer John一直努力让他的草地充满鲜美多汁的而又健康的牧草。可惜天不从人愿&#xff0c;他在植物大战人类中败下阵来。邪恶的乳草已经在他的农场的西北部份佔领了一片立足之地。草地像往常一样&#xff0c;被分割成一个高度為Y(1 < y < 100)…

s查找mysql服务_MySQL菜鸟实录(一):MySQL服务安装实战

CentOS 7基本信息系统版本&#xff1a; CentOS 7.3 64bit系统配置&#xff1a; 4vCPUs | 8GB磁盘空间&#xff1a;[rootecs-ce5a-0001 ~]# df -hFilesystem Size Used Avail Use% Mounted on/dev/vda1 40G 17G 22G 44% /devtmpfs 3.9G 0 3.9G 0% /devtmpfs 3.9G 0 3.9G 0% /dev…

实验一 线性表的顺序存储与实现_【自考】数据结构中的线性表,期末不挂科指南,第2篇

线性表这篇博客写的是线性表相关的内容&#xff0c;包括如下部分&#xff0c;先看下有木有期待啥是线性表线性表的顺序存储线性表的基本运算在顺序表上的实现线性表的链式存储线性表的基本运算在单链表上的实现循环链表与双向循环链表Over&#xff0c;内容还蛮多的&#xff01;…

TYVJ P1012 火柴棒等式 Label:枚举

背景 NOIP2008年提高组第二题描述 给你n根火柴棍&#xff0c;你可以拼出多少个形如“ABC”的等式&#xff1f;等式中的A、B、C是用火柴棍拼出的整数&#xff08;若该数非零&#xff0c;则最高位不能是0&#xff09;。用火柴棍拼数字0-9的拼法如图所示&#xff1a;注意&#xff…

python怎么开发软件_怎么使用python进行软件开发

一、下载pyinstaller 我使用的版本为PyInstaller-2.1&#xff0c;支持python版本2.3-2.7&#xff0c;点击这里下载。 二、安装pyinstaller 下载完成后&#xff0c;解压即可。我的解压目录为D:\Python27\PyInstaller-2.1\ 三、使用pyinstaller打包.py成.exe应用程序 1.注意使用前…

28、清华大学脑机接口实验组SSVEP数据集:通过视觉触发BCI[飞一般的赶脚!]

前言&#xff1a; 哈喽&#xff0c;最近对清华大学脑机接口的数据进行了尝试&#xff0c;输入到了DL模型中&#xff0c;以下是本人对于清华BCI数据的个人见解。 数据地址&#xff1a; 清华大学脑机接口研究组 (tsinghua.edu.cn) 打开网站可以看到有很多个数据&#xff0c;官…

python Pexpect

http://www.cnblogs.com/dkblog/archive/2013/03/20/2970738.htmlhttp://www.ibm.com/developerworks/cn/linux/l-cn-pexpect2/index.htmlhttp://www.cnblogs.com/dkblog/archive/2013/03/20/2970738.htmlpython Pexpect Pexpect 是一个用来启动子程序并对其进行自动控制的纯 P…

3dmax镜像后模型线条乱了_3dMax入门教程来啦!小白赶紧收藏!

3D Studio Max&#xff0c;常简称为3d Max或3ds MAX&#xff0c;是Discreet公司开发的&#xff08;后被Autodesk公司合并&#xff09;基于PC系统的三维动画渲染和制作软件&#xff0c; 3dmax软件主要功能有建模&#xff0c;动画&#xff0c;渲染&#xff0c;特效等&#xff0c;…

如何将多个一维列表转化为二维列表_数据分析2_如何处理一维、二维数据

吞一块大饼&#xff0c;还不如切成小块吃得香常见的数据集&#xff0c;要么是数列&#xff0c;要么是表格&#xff1b;因此&#xff0c;数据分析最首要的是&#xff0c;处理一维、二维数据。主要知识点可参考如图。如需要&#xff0c;可点击以下百度网盘链接下载数据分析基础知…

关于java中锁的面试题_Java面试题-Java中的锁

1. 如何实现乐观锁(CAS)&#xff1f;如何避免ABA问题&#xff1f;答&#xff1a;1)读取内存值的方式实现了乐观锁(比如&#xff1a;SVN系统)&#xff0c;方法&#xff1a;第一&#xff0c;比较内存值和期望值&#xff1b;第二&#xff0c;替换内存值为要替换值。2)带参数版本来…

NSUserDefaults

2019独角兽企业重金招聘Python工程师标准>>> NSUserDefaults 转载于:https://my.oschina.net/18829297883/blog/737931

什么是算术运算和逻辑运算_8086微处理器的算术和逻辑运算

什么是算术运算和逻辑运算逻辑指令 (Logical Instructions) a) AND: Logical AND a)AND&#xff1a;逻辑AND Atleast one of the operant should be a register or a memory operant both the operant cannot be a memory location or immediate operant. 操作中的至少一个应该…

h5引入json_Vue中如何使用本地Json文件?

我需要将菜单配置成Json文件&#xff0c;然后再程序中引入{{menu.name}}import menuListConfig from ../../config/menu.jsonexport default {name: "Sider",data(){return {menuList:JSON.parse(JSON.stringify(menuListConfig))}}}需要如何做&#xff0c;才能v-for…

python2和python3的默认编码_python2和python3哪个版本新

Python2 还是 Python3 &#xff1f; py2.7是2.x系列的最后一个版本&#xff0c;已经停止开发&#xff0c;不再增加新功能。2020年终止支持。 所有的最新的标准库的更新改进&#xff0c;只会在3.x的版本里出现。Python3.0在2008年就发布出来&#xff0c;而2.7作为2.X的最终版本并…