前缀树和后缀树

Trie树的应用:

   除了本文引言处所述的问题能应用Trie树解决之外,Trie树还能解决下述问题(节选自此文:海量数据处理面试题集锦与Bit-map详解):
3、有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
9、1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现?
10、 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。
13、寻找热门查询:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。
(1) 请描述你解决这个问题的思路;
(2) 请给出主要的处理流程,算法,以及算法的复杂度。
    有了Trie,后缀树就容易理解了。本文接下来的第二部分,介绍后缀树

前缀树:

前缀树又名字典树,单词查找树,Trie树,是一种多路树形结构,是哈希树的变种,和hash效率有一拼,是一种用于快速检索的多叉树结构。

典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

Trie树也有它的缺点,Trie树的内存消耗非常大。

性质:不同字符串的相同前缀只保存一份。

操作:查找,插入,删除。

举个栗子:给出一组单词,inn, int, at, age, adv,ant, 我们可以得到下面的Trie:

从上面可以发现一些Trie树的特性:

1)根节点不包含字符,除根节点外的每一个子节点都包含一个字符。

2)从根节点到某一节点的路径上的字符连接起来,就是该节点对应的字符串。

3)每个节点的所有子节点包含的字符都不相同。

4)每条边对应一个字母。每个节点对应一项前缀。叶节点对应最长前缀,即单词本身。

单词inn与单词int有共同的前缀“in”, 因此他们共享左边的一条分支,root->i->in。同理,ate, age, adv, 和ant共享前缀"a",所以他们共享从根节点到节点"a"的边。

前缀树的应用

前缀树还是很好理解,它的应用也是非常广的。

(1)字符串的快速检索

字典树的查询时间复杂度是O(logL),L是字符串的长度。所以效率还是比较高的。字典树的效率比hash表高。

hash表:

通过hash函数把所有的单词分别hash成key值,查询的时候直接通过hash函数即可,都知道hash表的效率是非常高的为O(1),当然这是对于如果我们hash函数选取的好,计算量少,且冲突少,那单词查询速度肯定是非常快的。那如果hash函数的计算量相对大呢,且冲突律高呢?这些都是要考虑的因素。

还有就是hash表不支持动态查询,什么叫动态查询,当我们要查询单词apple时,hash表必须等待用户把单词apple输入完毕才能hash查询。当你输入到appl时肯定不可能hash吧。

字典树(tries树):

对于单词查询这种,还是用字典树比较好,但也是有前提的,空间大小允许,字典树的空间相比较hash还是比较浪费的,毕竟hash可以用bit数组。

 

(2)字符串排序

从上图我们很容易看出单词是排序的,先遍历字母序在前面。

减少了没必要的公共子串。

 

(3)最长公共前缀

inn和int的最长公共前缀是in,遍历字典树到字母n时,此时这些单词的公共前缀是in。

(4)自动匹配前缀显示后缀

我们使用辞典或者是搜索引擎的时候,输入appl,后面会自动显示一堆前缀是appl的东东吧。

那么有可能是通过字典树实现的,前面也说了字典树可以找到公共前缀,我们只需要把剩余的后缀遍历显示出来即可。

后缀树

后缀树,就是把一串字符的所有后缀保存并且压缩的字典树。相对于字典树来说,后缀树并不是针对大量字符串的,而是针对一个或几个字符串来解决问题。比如字符串的回文子串,两个字符串的最长公共子串等等。

性质:一个字符串构造了一棵树,树中保存了该字符串所有的后缀。

操作:就是建立和应用。

 

(1)建立后缀树

比如单词banana,它的所有后缀显示到下面的。0代表从第一个字符为起点,终点不用说都是字符串的末尾。

以上面的后缀,我们建立一颗后缀树。如下图,为了方便看到后缀,我没有合并相同的前缀。

前面简介的时候我们说了,后缀树是把一个字符串所有后缀压缩并保存的字典树。所以我们把字符串的所有后缀还是按照字典树的规则建立,就成了下图的样子。这就是后缀树的压缩,可见更加节省空间。

最长回文问题的解决:

思维的突破点在于考察回文的半径,而不是回文本身。所谓半径,就是回文对折后的字串。比如回文MADAM 的半径为MAD,半径长度为3,半径的中心是字母D。显然,最长回文必有最长半径,且两条半径相等。还是以MADAM为例,以D为中心往左,我们得到半径 DAM;以D为中心向右,我们得到半径DAM。二者肯定相等。因为MADAM已经是单词XMADAMYX里的最长回文,我们可以肯定从D往左数的字串 DAMX与从D往右数的子串DAMYX共享最长前缀DAM。而这,正是解决回文问题的关键。现在我们有后缀树,怎么把从D向左数的字串DAMX变成后缀 呢?

    到这个地步,答案应该明显:把单词XMADAMYX翻转(XMADAMYX=>XYMADAMX,DAMX就变成后缀了)就行了。于是我们把寻找回文的问题转换成了寻找两坨后缀的LCA的问题。当然,我们还需要知道 到底查询那些后缀间的LCA。很简单,给定字符串S,如果最长回文的中心在i,那从位置i向右数的后缀刚好是S(i),而向左数的字符串刚好是翻转S后得到的字符串S‘的后缀S'(n-i+1)。这里的n是字符串S的长度。

    可能上面的阐述还不够直观,我再细细说明下:

    1、首先,还记得本第二部分开头关于后缀树的定义么: “先说说后缀的定义,顾名思义,甚至通俗点来说,就是所谓后缀就是后面尾巴的意思。比如说给定一长度为n的字符串S=S1S2..Si..Sn,和整数i,1 <= i <= n,子串SiSi+1...Sn便都是字符串S的后缀。”

    以字符串S=XMADAMYX为例,它的长度为8,所以S[1..8], S[2..8], ... , S[8..8]都算S的后缀,我们一般还把空字串也算成后缀。这样,我们一共有如下后缀。对于后缀S[i..n],我们说这项后缀起始于i。

S[1..8], XMADAMYX, 也就是字符串本身,起始位置为1
  S[2..8], MADAMYX,起始位置为2
     S[3..8], ADAMYX,起始位置为3
       S[4..8], DAMYX,起始位置为4
          S[5..8], AMYX,起始位置为5
            S[6..8], MYX,起始位置为6
               S[7..8], YX,起始位置为7
                 S[8..8], X,起始位置为8
                                  空字串,记为$。

    2、对单词XMADAMYX而言,回文中心为D,那么D向右的后缀DAMYX假设是S(i)(当N=8,i从1开始计数,i=4时,便是S(4..8));而对于翻转后的单词XYMADAMX而言,回文中心D向右对应的后缀为DAMX,也就是S'(N-i+1)((N=8,i=4,便是S‘(5..8)) 。此刻已经可以得出,它们共享最长前缀,即LCA(DAMYX,DAMX)=DAM。有了这套直观解释,算法自然呼之欲出:

预处理后缀树,使得查询LCA的复杂度为O(1)。这步的开销是O(N),N是单词S的长度 ;
对单词的每一位置i(也就是从0到N-1),获取LCA(S(i), S‘(N-i+1)) 以及LCA(S(i+1), S’(n-i+1))。查找两次的原因是我们需要考虑奇数回文和偶数回文的情况。这步要考察每坨i,所以复杂度是O(N) ;
找到最大的LCA,我们也就得到了回文的中心i以及回文的半径长度,自然也就得到了最长回文。总的复杂度O(n)。 
 

参考博客:

https://blog.csdn.net/v_july_v/article/details/6897097

https://blog.csdn.net/u013949069/article/details/78056102

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

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

相关文章

学长启蒙

2017.06.20506实验室家才学长课堂总结&#xff1a;第一次正式听家才学长的课堂&#xff0c;课堂主要目的学长凭自己的经验和丰富的知识带领我们入门机器学习&#xff0c;认识机器学习&#xff0c;学习机器学习。 课堂第一项&#xff0c;首先为方便与学长交流&#xff0c;认识&a…

A*算法启发式搜索

最短路径算法&#xff1a; A*算法擅长解决静态路径中最短距离问题&#xff0c;而又不同于Dijkstra算法和Floyd算法&#xff0c;该算法综合了BFS和Dijkstra算法优点&#xff1a;在进行启发式搜索提高算法效率的同时&#xff0c;可以保证找到一条最优路径(基于评估函数&#xff0…

基于维特比算法的概率路径

简介&#xff1a; 维特比算法(Vieterbi algorithm)是一种动态规划算法&#xff0c;探索出很多预测天气的方法&#xff0c;这种基于经验的预测方式&#xff0c;是一种基于历史数据的概率模型。 思想 维特比算法的思想是假设某一个数据的当前状态是依赖于它的前一个状态&#…

Python的Pexpect库

Pexpect 是一个用来启动子程序并对其进行自动控制的纯 Python 模块。 Pexpect 可以用来和像 ssh、ftp、passwd、telnet 等命令行程序进行自动交互。本文主要是针对ssh远程登录&#xff0c;然后执行简单的新建文件夹与拷贝任务Pexpect 的安装&#xff1a;下载&#xff1a;https:…

k-shingles和MinHash优秀文章保存

minhash原理解释&#xff1a;https://www.cnblogs.com/sddai/p/6110704.html k-shingles和minhash使用原理&#xff1a;https://blog.csdn.net/aspirinvagrant/article/details/41281101 代码java实现&#xff1a;https://blog.csdn.net/remoa_dengqinyi/article/details/728…

Python 数据库连接

#!/usr/bin/env python #-*-coding:utf-8-*- #异常处理&#xff0c;with的使用&#xff0c; class Mycontex(object):def __init__(self,name):self.namenamedef __enter__(self):print("__enter__")return selfdef do_self(self):print(do_self)def __exit__(self,e…

中文分词测试语句

研究生命科学研究生命令本科生我从马上下来我马上下来北京大学生喝进口红酒在北京大学生活区喝进口红酒从小学电脑从小学毕业美军中将竟公然说新建地铁中将禁止商业摊点这块地面积还真不小地面积了厚厚的雪让我们以爱心和平等来对待动物阿美首脑会议将讨论巴以和平等问题锌合金…

socket网络编程udp

#!/usr/bin/env python #-*-coding:utf-8-*- #udp socketserver客户端 import socketHOST127.0.0.1 PORT3214 ssocket.socket(socket.AF_INET,socket.SOCK_DGRAM)data你好&#xff01; s.sendto(data.encode(utf-8),(HOST,PORT))while data!bye:datbwhile len(dat)0:dat,addrs.…

几种分类器小结

朴素贝叶斯分类器是假设数据样本特征完全独立&#xff0c;以贝叶斯定理为基础的简单概率分类器。AdaBoost算法的自适应在于前一个分类器产生的错误分类样本会被用来训练下一个分类器&#xff0c;从而提升分类准确率&#xff0c;但是AdaBoost算法对于噪声样本和异常样本比较敏感…

socket网络编程tcp

#!/usr/bin/env python #-*-coding:utf-8-*- #tcp 客户端import socketHOST127.0.0.1 POST3214ssocket.socket() try:s.connect((HOST,POST))data你好&#xff01;while data:s.sendall(data.encode(utf-8))datas.recv(1024)print(Receive from Server:\n,data.decode(utf-8))d…

聚类算法篇章总结

主要的距离计算方法包括&#xff1a; 最短距离法(通过样本数值之间的距离计算&#xff0c;然后将距离值最小的样本进行合并的过程)最长距离法中间距离法重心法(重心聚类法&#xff1a;将两个聚类中心点的距离定义为两个类的重心距离&#xff0c;而类的重心为属于该类的样本的平…

socket网络编程多线程

#!/usr/bin/env python #-*-coding:utf-8-*- #多进程 import threading import time def thfun():s0for i in range(30):sitime.sleep(0.1)print(s)class MyThread(threading.Thread):def run(self):s0for i in range(30):sitime.sleep(0.1)print(s)if __name____main__:#ths[t…

学习之道

对待人生的任何事情都要&#xff1a;抓大放小&#xff0c;要事为先 对于一个以前从来没有接触过java的人&#xff0c;java无疑是庞大的&#xff0c;似乎每个领域都可以拓展开来都是一片开阔地&#xff0c;每一个领域要想深入接触到每一个细节所耗费的精力都是巨大的。这个时候大…

socket网络编程ftp

#!/usr/bin/env python #-*-coding:utf-8-*- #ftp客户端 import os import socket import threading import socketserver#下载文件 def get_file(host,port,filepath):ssocket.socket()s.connect((host,port))filepathos.path.join(.,bakc,filepath)fopen(filepath,wb)dataTr…

python类似于java的重写toString方法

python类中的特殊方法&#xff1a; __str__(self)用来重写 people类&#xff1a; class People:所有员工的基类#构造函数def __init__(self, user_id, user_name):self.user_id user_idself.user_name user_name#析构函数def __del__(self):class_name self.__class__.__n…

socket网络编程实现远程备份

#!/usr/bin/env python #-*-coding:utf-8-*- #GUI设计与构建 客户端建立from tkinter import * from tkinter.ttk import * import socket import struct#启动服务 def start(host,port):pass#我的控件 class MyFrame(Frame):def __init__(self,root):super().__init__(root)se…

python正则表达式使用

模板代码&#xff1a; import re #python 自1.5版本增加了re模块&#xff0c;它提供了Perl风格的正则表达式模式 print(re.match(www, www.baidu.com).span()) #在起初位置匹配 line "Cats are smarter than dogs" matchObj re.match( r(.*) are (.*?) .*, line,…

Q-学习,马克尔决策过程:强化学习

马尔可夫决策过程 马尔可夫决策过程是基于马尔可夫过程理论的随机动态系统的最优决策过程。马尔可夫决策过程是序贯决策的主要研究领域。它是马尔可夫过程与确定性的动态规划相结合的产物&#xff0c;故又称马尔可夫型随机动态规划&#xff0c;属于运筹学中数学规划的一个分支…

python对象使用

模板代码&#xff1a; class People:所有员工的基类#构造函数def __init__(self, user_id, user_name):self.user_id user_idself.user_name user_name#析构函数def __del__(self):class_name self.__class__.__name__print(class_name, 销毁)def get_info(self):print(&qu…