fst 共享后缀_trie、FSA、FST(转)
add by zhj: 在学习Lucene的存储结构时,看到其使用了FST,这篇文章写的不错。
trie,FSA,FST都是用来解决有限状态机的存储,trie是树,它进一步演化为FSA和FST,这两者是图
该文的原标题是“使用自动机来索引1,600,000,000个键”,我改了一下,原标题其实是
说这三类数据结构的使用场景。
有限状态机(FSM, finite state machine)可以用来紧密地存储有序集合和有序键值对,并且可以实现快速搜索。本文中,我会表明怎样用FSM来作为数据结构存储这样的数据。
FSM作为数据结构
FSM是一个状态的集合和状态转移的集合。一个起始状态,0个或多个结束状态。一个FSM在同一时间只有一个状态。
FSM非常常见,并且可以用来描述一系列过程。比如我家猫咪Cauchy一天的日常生活:
里面有一些”asleep”或者”eating”的状态,一些转移”food is served”, “something moved”。这里没有结束状态,如果结束了,那真是太恐怖了!
FSM近似的表达了现实中的情况。Cauchy不可能同时吃饭和睡觉,这跟FSM中同一时刻只有一个状态是一样的。并且,从一个状态转移到另一个状态需要外部环境的一个输出。需要睡觉,可能是因为”吃饱了”, “累了”等等。不管他睡得多死,”听到外面的声音”,它总会醒过来。
有序集合
有序集合里的键按照字典序排序。典型的应用是二叉查找树和B树。无序集合,典型应用就是哈希表。这里,我们先描述一个确定无环有限状态接收器(deterministic acyclic finite state acceptor),即FSA。
一个FSA需要满足以下条件:
确定性的。给定已给输入,最多只能转移到一个状态。
无环的。不能反序遍历。
接收器。FSA可以接收一系列特定的输入。
那么,怎么用这些特性来表示一个集合呢。诀窍在于,key作为FSA的状态转移。这样,给定一个输入key,我们可以知道这个key这个key是否在FSA中。
假设一个集合,只有一个key”jul”。FSA就像下面这样:
这时候如果问FSA,是否包含”jul”。处理顺序如下:
给定j,FSA状态从0变为1.
给定u,FSA状态从1变为2.
给定l,FSA状态从2变为3.
输入结束,这时候判断一下FSA是否处在final状态(图中用双圈表示),表明jul确实在set中。
这时候如果问FSA,是否包含”jun”。处理顺序如下:
给定j,FSA状态从0变为1.
给定u,FSA状态从1变为2.
给定l,FSA不动,处理结束。
FSA不动,因为状态2只接收’l’的转移,但是当前输入为’n’。因此处理结束,也表明集合中不包含”jun”。
这时候如果问FSA,是否包含”ju”。处理顺序如下:
给定j,FSA状态从0变为1.
给定u,FSA状态从1变为2.
判断一下,此时是否处于final状态。
值得注意的是,判断一个key是否存在,受限于key的长度,而不是set的大小。
下面把key”mar”添加到FSA中去,这时候FSA的表现如下:
FSA变得稍微复杂一点,状态0可以有两个转移。如果起始输入mar,它会先转移到1状态。
还有一个需要注意的是,状态3被jul和mar两个key共享。即,状态3可以由l和r转移过来。这种共享的方式,可以用更少的空间保存更多的信息。
如果再加入jun,FSA表现如下:
看到变化了么?只有一点点变化。FSA看起来和之前的几乎没什么区别。唯一变化的地方在状态5多了一个转移。FSA其实没有新增状态节点,因为jun和jul共享了前缀ju
下面展示一个更复杂的FSA,包含三个key,october,november,december。
因为有相同的后缀ber,在FSA中只需要编码一次就行了。两个key有更大的相同的后缀,ember。
在介绍FST之前,我们先看看,如何来遍历FSA中所有的key呢?
为了阐述这个过程,还用一个之前的一个简单的图,有三个key,jul,jun和mar。
遍历方式如下:
初始化状态0
移动到状态4,把j添加到key中
移动到状态5,把u添加到key中
移动到状态3,把l添加到key中,输出jul
返回状态5,把key中的l抛弃掉
移动到状态3,把n添加到key中,输出jun
返回状态5,把key中的n抛弃掉
返回状态4,把key中的u抛弃掉
返回状态0,把key中的j抛弃掉
移动到状态1,把m添加到key中
移动到状态2,把a添加到key中
移动到状态3,把r添加到key中,输出mar
这个算法直接应用一个栈存储访问过的状态,和一个栈存储相应的转移。时间复杂度为O(n),空间复杂度O(k),k是set中最长的键的大小。
有序map
和有序集合类似,只是多了一个输出。有序map常用在二叉查找树和b树,无序map就是hashtbale。这里我们介绍一个deterministic acyclic finite state transducer,确定无环有限状态转移器,FST。
FST满足以下特性:
确定性。
无环。
一个转移器。给定一系列输入,会输出一个值。当且仅当输入会达到FST的final状态。
FST和FSA很像,但是对于一个key,FSA只回答了”yer or no”,FST不仅回答”yes or no”,还好返回和这个key相关的一个值。
在有序集合中,只需要把key保存在转移时。但是在这里,还需返回与key对于的value。
一种方法是,在每次转移的时候添加一些值。当输入序列在状态之间转移的时候,输出序列也在慢慢增加。
还是看一个简单的例子吧。map中只包含一个数据jul,对应的value为7:
这和上面的集合差不多,只是在第一个转移状态j之后多了一个相关联的输出7.另外的转移u和l对应的输出都是0,所以图中就不显示了.
如果要判断,FST中是否存在key”jul”,并且需要对应的返回值,处理过程如下:
初始化value为0
给定输入j,FST从状态0转移到1,value+7
给定输入u,FST从状态1转移到2,value+0
给定输入l,FST从状态2转移到3,value+0
输入结束,状态3为final状态,因此key存在,value为7
下面把k-v,”mar 3”添加到FST中
在起始节点,多了一个新的转移m,对应输出为3.如果我们查询jul,那么应该和上面是一样的处理过程。
继续,当添加一个有相同前缀的key,会发生什么呢?
添加key jun,value 6
在状态5和状态3之间添加了一个转移n。但是还有另外两个变化
0->4转移j输入对应输出从7变成了6.
5->3转移l输入对应输出从0变成了1.
这个变化之后们可以正确查询jun和jul,并且返回正确的值。
这种key的属性确保了,即使共享前缀,对于每一个key,然后只有一个唯一的路径可以贯穿整个machine。因此,每个key也有唯一的value。我们要做的就是怎么把这些输出放在转移中去。
其实不仅可以共享前缀,还可以共享后缀。对于两个key tuesday和thursday,分别对于输出3和5.
这两个key有相同的前缀t,相同的后缀sday,按照图里的方式可以保证输出的正确性。
这里在描述输出的时候,其实有一点局限,如果输出不是整形。确实,在FST里用做输出的类型必须满足以下特性:
加法
减法
取前缀(对于整数来说就是min)
构建
Trie树构建
trie树,前缀树。和FSA的区别在于,FSA可以共享前缀和后缀。对于键mon,tues,thus来说,FSA如下:
而trie树只共享前缀,如下:
构建trie树很直接的,对于一个给定的输入,只需要去看看有没有相同的前缀。直到找出相同的前缀,余下的直接转移到一个final状态就可以了。
FSA构建
FSA和trie的区别在于,共享后缀。因此一个FSA的空间会比trie少很多,但是构建起来却更复杂,因此我们如果按照key的字典序插入的话,会好很多,还是用图片来说明。
对于三个key,mon,tues和thurs。按照字典序,插入顺序mon,thurs和tues。先插入mon:
下面插入thurs:
插入thurs的时候,会导致之前的mon被冻结。当FSA中一部分被冻结的时候,我们知道,它以后再也不会被更改了。因为按照字典序排序的,后面的key肯定都是大于等于thurs的。因此不会和mon有相同前缀的key插入了。蓝色的state代表被冻结住,以后不会被更改但是可以被复用。
虚线的状态表示thurs还没有被真正加入到FSA中去,下面插入tues:
在这一步里,我们可以确定hurs会被冻住。因为将会不会有和它有相同前缀的词插入进来了。因为thurs和mon可以有相同的final state了。
这里状态4仍然是虚线,因为还不能确定t开头的key还有没有了。如果下面插入zon:
看到,这时状态4已经被冻住了,因为不会在有t开头的key出现了,另外thurs和tues有一个共同的后缀s,因此状态7和状态9被合并了。
最后,在结束操作以后,把FSA的最后一部分冻住,一个完整的没有重复的结构如下:
因为mon和zon有相同的后缀,因此它们除了第一个状态转移不一样,剩下的可以重复利用。
FST构建
下面快速说一下FST构建,插入键值对 mon-2,tues-3,thurs-5.
直接上图,插入mon-2
对于第一步,我们也可以这样分配输出的值
其实这样也是可以的,但是在算法上来说,把输出放在靠近初始状态的地方,代码写起来更简单。
插入thurs-5
插入tues-3
在把状态0-4之间的输出从5变为3的之后,需要把4之后所有的输出全部加2,除了新添加的key,这样就可以保持输出的平衡。
下面插入一个tye-99
最后的完全形态
fst 共享后缀_trie、FSA、FST(转)相关推荐
- fst 共享后缀_FST源代码解读1——FST是什么
在之前研究IK的时候,看了他存储词典的格式,接触到了前缀树这种数据结构,他的好处是可以共享前缀,但是并不能共享后缀,而FST就能共享后缀,实现更加高效的存储或者查找.当然FST比前缀树添加了一个要求: ...
- fst 共享后缀_关于Lucene的词典FST深入剖析
核心是在于如何快速的依据 查询词 快速的查找到所有的相关文档,这也是 倒排索引(Inverted Index) 的核心思想.那么如何设计一个快速的(常量,或者1)定位词典的数据结构就显得尤其重要.简单 ...
- fst 共享后缀_FST源代码解读2——FST的生成
本篇博客介绍FST的生成.它使用的是生成器模式,因为FST的生成太复杂了,所以必须使用生成器模式,他的类是:org.apache.lucene.util.fst.Builder.Builder(INP ...
- fst 共享后缀_谈谈lucene中的FST
FST是lucene中用来存储字典,并进行检索的核心数据结构,我记得在lucene3.0版本前都用的是跳跃链表(不清楚的同学以后我会专门写一篇磁盘索引技术来说清楚,网上也有相关资料),实际上采用该数据 ...
- fst java_java快速序列化库FST
FST fast-serialization 是重新实现的 Java 快速对象序列化的开发包.序列化速度更快(2-10倍).体积更小,而且兼容 JDK 原生的序列化.要求 JDK 1.7 支持. Ma ...
- java fst 入门 例子_Redis 使用 fst 进行序列化
添加 maven 依赖 de.ruedigermoeller fst 2.57 实现 RedisSerializer 接口 public class FSTSerializer implements ...
- lucene随笔-FST(Finite State Transducer)有限状态传感器
lucene版本:6.5.1 有限状态传感器,FST(Finite State Transducer)在lucene中扮演着非常重要的一个角色,在4.0后的版本lucene大量使用了这种数据结构,主要 ...
- 关于Lucene的词典FST深入剖析
搜索引擎为什么能查询速度那么快? 核心是在于如何快速的依据查询词快速的查找到所有的相关文档,这也是倒排索引(Inverted Index)的核心思想.那么如何设计一个快速的(常量,或者1)定位词典的数 ...
- Elasticsearch中FST与前缀搜索
FST的基本概念 FST(Finite-State Transducer)是一种有限状态自动机,可以将一组输入符号映射为一组输出符号.FST由一组状态和一组转移组成,状态可以是起始状态.接受状态或既是 ...
- Lucene 倒排索fst引原理与实现
整理下几篇博客 1 Lucene 4.X 倒排索引原理与实现: (3) Term Dictionary和Index文件 (FST详细解析) https://www.cnblogs.com/forfut ...
最新文章
- 系统怎么手动打补丁_韩国服务器不稳定怎么办?
- SpringMVC整合fastdfs-client-java实现web文件上传下载
- VMware 收购 Kubernetes 初创公司 Heptio
- JSP第四课:用户注册登录设计(内置对象使用)
- swith语句的较安全用法
- Cloud Foundry中warden的网络设计实现——iptable规则配置
- LIS(最大上升子序列)
- s3c6410 开发板Linux系统支持 K9GAG08U0E的方法
- 【机器学习】怎样将Embedding融入传统机器学习框架?
- Apache RocketMQ部署文档
- PHP 多参数方法的重构
- kali的软件包安装源配置
- sentinel 时间窗口的实现
- 使软件可二次开发_RobotStudio二次开发:Smart组件I/O信号声明
- 奇怪的问题:为什么手机过一年就变得很慢?
- cat和EOF的组合妙用
- 教你用MSChart控件绘制正态分布图形
- easypoi 表头数据导入_使用easypoi根据表头信息动态导出excel
- Xposed反射大师脱壳教程
- 日语 敬体 简体 作文 对话