今天这篇文章源于上周在工作中解决的一个实际问题,它是个比较普遍的问题,无论做什么开发,估计都有遇到过。具体是这样的,我们有一份高校的名单(2657个),需要从海量的文章标题中找到包含这些高校的标题,其实就是模糊查询(关注公众号 渡码, 回复关键词 trie 获取源码)。对应的伪代码如下

selected_titles = []
for 标题 in 海量标题:
for 高校 in 高校名单:
if 标题.contains(高校):
selected_titles.add(标题)
break

如果是大数据开发,对应的SQL的伪代码是这样的

select title
from tb
where
title rlike '清华大学|北京大学|...2657个高校'

上面这两种做法都能实现我们的需求,但它们的共同问题是查询效率太低。如果我们要匹配的高校不是2657个而是几十万甚至上百万,那这种方式耗费时之久是不可想象的。

优化这类问题通常需要在数据结构上做文章,这个问题中我们能优化的数据结构也只有“高校名单”这个了,上面的伪代码中我们存放“高校名单”的数据结构是数组,当我们查找某个title是否包含某个高校的时候,需要从头到尾遍历一遍“高校名单”,并且名单越长,遍历耗时就越长。

清楚了数组这种数据结构的缺点后,接下来我们重点要做的就是寻找一个数据结构可以做到在不遍历整个“高校名单”的情况下就可以完成模糊查询。这个数据结构就是我们今天要介绍的 Trie 树,冷眼一看这个单词有点陌生,又是一个树型结构,感觉会很复杂似的,实际上这个数据结构的设计思想非常简单,一学就会。

下面我们就来学习一下 Trie 树。为了方面讲解,假设“高校名单”里只有下面5个元素

ABC、ABD、BCD、BCE、C、CAB、CDE

对应的两种数据结构如下:

抛开这两种数据结构查找的时间复杂度,我们先从直观上看看为什么 Trie 树的查找效率要比数组高。假设我们要查找,“CDE”这个字符串,在数组结构中,我们要遍历一遍数组,比较7次才能找到结果,做了比较多的“无用功”。而在 Tire 树中只需要比较3次就可以找到,它的优势非常明显,由于树型结构我们根本不用考虑左侧A、B开头的两个分支,这就大大减少了比较的次数,从而减少“无用功”。下面用一个动画来演示一下如何创建 Trie 树,以及在 Trie树上查找字符串(如果视频播放不了可以看源码目录中的gif)

树的建立过程其实就是遍历字符串每个元素并在树上建立相应的节点。字符串查找过程其实就是按照字符串对树进行遍历。Trie 树的建立与字符串查找还是比较简单的。

不知道大家是否注意到上图 Trie 树中的节点有两种颜色——白色和绿色。绿色节点代表从根节点到当前节点的字符串是“高校名单”中的字符串,也就是我们建立 Trie 树用到的字符串。以最左侧的叶子结点“C”为例,它代表“ABC”字符串是“高校名单”中的字符串。同理,字符串“AB”就不是“高校名单”里的元素,因为“B”节点不是绿色的,因此当我们在这棵树上查找字符串“AB”时,是查不到的。这一点需要大家注意,下面编码中我们也有体现。

另外,有朋友可能会有疑问,我们最开始的需求不是模糊查询吗,在 Trie 树讲解这部分怎么都在说字符串全词(精确)匹配。这是因为全词匹配是 Tire 树支持的最基本的查找方式,在此基础上,我们做一些变通就可以很容易实现模糊匹配。

接下来,我们就来看看代码实现(Python版),首先创建两个数组

colleges = utils.read_file_to_list('key_words.txt')
titles = utils.read_file_to_list('titles.txt')

colleges就是我们一直在说的“高校名单”,titles便是“海量标题”,它们都是一维数组,数组每个元素都是一个字符串。

再来编写 Trie 树相关的代码,如果理解了 Trie 树的设计思想,再编写下面的代码其实很容易。首先要定义一个类代表 Trie 树节点

class TrieNode:
def __init__(self):
self.nodes = dict()
# is_end=True 代表从根节点到当前节点构造Trie树的字符串(出现在“高校名单”里)
self.is_end = False

is_end=True就是我们上面说的绿色节点。

再来编写创建 Trie 树的代码,代码在 TrieNode 类中

def insert_many(self, items: [str]):
"""
支持输入字符串数组,直接构造一个 Trie 树
:param items: 字符串数组
:return: None
"""
for word in items:
self.insert(word)

def insert(self, item: str):
"""
向 Trie 树插入一个短语
:param item: 待插入的字符串
:return: None
"""
curr = self
for word in item:
if word not in curr.nodes:
curr.nodes[word] = TrieNode()
curr = curr.nodes[word]
curr.is_end = True

再来编写查找 Trie 树的代码,代码在 TrieNode 类中

def suffix(self, item: str) -> bool:
"""
匹配前缀,也就是判断item字符串是否是以“高校名单”中某个字符串开头
:param item: 待匹配字符串
:return: True or False
"""
curr = self
for word in item:
if word not in curr.nodes:
return False
curr = curr.nodes[word] # 取得子节点
if curr.is_end: # 如果is_end=True说明当前字符串包含了“高校名单”的某个字符串
return True
return False # 未匹配上

这里并不是全词匹配,而是前缀匹配,也就是判断待查找的字符串item是否是以“高校名单”中某个字符串开头。

再来编写模糊匹配,代码在 TrieNode 中

def infix(self, item: str) -> bool:
for i in range(len(item)):
sub_item = item[i:] # 将待查找的字符串分成不同子串
# 如果子串的前缀在 Trie 树中能匹配上
# 说明待查找的字符串item中包含“高校名单”中的元素,
# 即实现了 tile rlike '清华大学|北京大学|...其他大学' 的功能
if self.suffix(sub_item):
return True
return False

这里其实就是把待查找字符串item分成不同子串去做前缀匹配,如果子串匹配上,那就说整个字符串item就包含了“高校名单”里面的某个字符串。

最后,我们运行一下上面的代码,并记录查找时间,与最开始数组结构那一版做个对比。代码如下

# 数组版本
cnt = 0
start_time = int(time.time() * 1000)
for title in titles:
for x in colleges:
if x in title:
cnt += 1
break

end_time = int(time.time() * 1000)
print(cnt)
print('spend: %.2fs' % ((end_time - start_time) / 60.0))

# Trie 树版本
root = TrieNode()
root.insert_many(colleges)
cnt = 0
start_time = int(time.time() * 1000)
for title in titles:
if root.infix(title):
cnt += 1

end_time = int(time.time() * 1000)
print(cnt)
print('spend: %.2fs' % ((end_time - start_time) / 60.0))

输出结果如下:

5314
spend: 9.13s
5314
spend: 0.23s

可以看到,用数组匹配用了9s,而用 Trie 树匹配仅用0.23s!

今天介绍的这种提高海量数据模糊查询性能的方式是通过写代码的方式实现的,对于经常写 SQL 的大数据开发者来说,要把它用起来只是建个 UDF 就可以了,需要在 UDF 的初始化代码中用“高校名单”建立一颗 Trie 树。

今天的内容就分享到这里了,希望对你有帮助。公众号回复关键词 trie 获取完整源代码

欢迎公众号「渡码」,输出别地儿看不到的干货。

Trie树-提高海量数据的模糊查询性能的更多相关文章

  1. [转]ORACLE中Like与Instr模糊查询性能大比拼

    instr(title,'手册')>0  相当于  title like '%手册%' instr(title,'手册')=1  相当于  title like '手册%' instr(titl ...

  2. Like与Instr模糊查询性能

    项目中用到like模糊查询,但是总觉的太小家子气,有没有高逼格的呢? instr(title,'手册')>0 相当于 title like '%手册%' instr(title,'手册')=1 ...

  3. mysql 优化海量数据插入和查询性能

    对于一些数据量较大的系统,数据库面临的问题除了查询效率低下,还有就是数据入库时间长.特别像报表系统,每天花费在数据导入上的时间可能会长达几个小时或十几个小时之久.因此,优化数据库插入性能是很有意义的. ...

  4. Trie树入门

    Trie树入门 貌似很多人会认为\(Trie\)是字符串类型,但是这是数据结构!!!. 详情见度娘 下面开始进入正题. PS:本文章所有代码未经编译,有错误还请大家指出. 引入 先来看一个问题 ​ 给 ...

  5. 整合hibernate的lucene大数据模糊查询

      大数据模糊查询lucene 对工作单使用 like模糊查询时,实际上 数据库内部索引无法使用 ,需要逐条比较查询内容,效率比较低在数据量很多情况下, 提供模糊查询性能,我们可以使用lucene全文 ...

  6. sql查询性能调试,用SET STATISTICS IO和SET STATISTICS TIME---解释比较详细

            一个查询需要的CPU.IO资源越多,查询运行的速度就越慢,因此,描述查询性能调节任务的另一种方式是,应该以一种使用更少的CPU.IO资源的方式重写查询命令,如果能够以这样一种方式完成查 ...

  7. Trie树【字典树】浅谈

    最近随洛谷日报看了一下Trie树,来写一篇学习笔记. Trie树:支持字符串前缀查询等(目前我就学了这些qwq) 一般题型就是给定一个模式串,几个文本串,询问能够匹配前缀的文本串数量. 首先,来定义下 ...

  8. Trie树的创建、插入、查询的实现

    原文:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28977986&id=3807947 1.什么是Trie树 Tr ...

  9. Entity Framework Code First+SQL Server,改变聚集索引,提高查询性能

    .net Entity Framework(调研的是Entity Framework 4.0) code first方式生成数据库时,不能修改数据库表的索引,而SQLServer默认会把数据表的主键设 ...

随机推荐

  1. Tensorboard详解(下篇)

    间提壶华小厨 1 Tensorflow监控指标可视化 除了GRAPHS栏目外,tensorboard还有IMAGES.AUDIO.SCALARS.HISTOGRAMS.DISTRIBUTIONS.FR ...

  2. coding++:thymelef 模板报错 the entity name must immediately follow the '&' in the entity reference

    thymelef模板里面是不能实用&符号的 要用&转义符代替,官网也有文档说明可以用官方的通配符代替, 官方文档 http://www.thymeleaf.org/doc/tutori ...

  3. 常见Web安全漏洞--------XSS 攻击

    1,XSS 攻击 XSS攻击使用Javascript脚本注入进行攻击 例如在提交表单后,展示到另一个页面,可能会受到XSS脚本注入,读取本地cookie远程发送给黑客服务器端. <script& ...

  4. iOS 重构AppDelegate

    一.Massive AppDelegate AppDelegate 是应用程序的根对象,它连接应用程序和系统,确保应用程序与系统以及其他应用程序正确的交互,通常被认为是每个 iOS 项目的核心. 随着 ...

  5. js中写laravel模板blade语法和PHP逻辑解决方法

    在js中是否能够执行blade的语法?或者说在js中能否处理PHP逻辑呢? 答案是,当然的 下面来看需求:在提交表单,完成入库操作后,使用 return redirect(route('admin.u ...

  6. 1025 PAT Ranking (25 分)

    Programming Ability Test (PAT) is organized by the College of Computer Science and Technology of Zhe ...

  7. vs中出现CL.exe已退出的情况总结

    1.函数具有返回值 在定义时没有加上返回值 2.使用未初始化的内存 比如 #include<stdio.h> int main() { int a; printf("%d&quo ...

  8. NAT及静态转换,动态转换及PAT

    NAT及静态转换,动态转换及PAT 案例1:配置静态NAT 案例2:配置端口映射 案例3:配置动态NAT 案例4:PAT配置 案例5:办公区Internet的访问 1 案例1:配置静态NAT 1.1 ...

  9. 在线优化算法 FTRL 的原理与实现

    在线学习想要解决的问题 在线学习 ( \(\it{Online \;Learning}\) ) 代表了一系列机器学习算法,特点是每来一个样本就能训练,能够根据线上反馈数据,实时快速地进行模型调整,使得 ...

  10. SpringMVC(二):使用注解开发

    本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出一遍就懂!b站搜索狂神说或点击下面链接 https://space.bilibili.com/95256449?spm_id_from=33 ...