精华内容
下载资源
问答
  • 前缀树
    千次阅读
    2018-12-15 17:25:22

    最近看代码,发现了一个敏感词检测是用前缀树写的,看起来速度蛮快,毕竟是拿空间换时间,LOG倍速。但是缺点也很明显,待检测文本需要与敏感词词库中的值完全匹配。所以对于简短的词法比较合适。

    原理:

    1. 每一个节点可以有多个子节点
    2. 节点“存储”字符, 节点与节点之间的连线自动形成单词。 如a节点与d节点,之间的连线就是单词 ad
    3. 节点可能是叶子节点,此时也是一个单词的“终点”,否则是其他拥有相同前缀的节点的“过客”, wordcount要加一。
    4. 删除一个单词,则对应节点上的“过客”都要减一,直至减至叶子节点。
    # coding: utf8
    MAX_TREE_WIDTH = 26
    INIT_CHAR = 'a'
    forbiddenwords = """
    fuck
    fucker
    damn
    silly
    """
    class TrieNode(object):
        def __init__(self):
            self.nodes = [None] * MAX_TREE_WIDTH
            self.wordcount = 0
            self.isend = 0
    
    class TrieTree(object):
        def __init__(self):
            self.root = TrieNode()
    
        def add(self, word):
            word = word.lower()
            curnode = self.root
            for char in word:
                index = ord(char) - ord(I
    更多相关内容
  • Trie树(前缀树

    2021-02-08 03:57:52
    Trie,即字典,又称单词查找或键,是一种形结构,是一种哈希的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少...

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

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

    基本性质

    1,根节点不包含字符,除根节点意外每个节点只包含一个字符。

    2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。

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

    优点:

    可以最大限度地减少无谓的字符串比较,故可以用于词频统计和大量字符串排序。

    跟哈希表比较:

    1,最坏情况时间复杂度比hash表好

    2,没有冲突,除非一个key对应多个值(除key外的其他信息)

    3,自带排序功能(类似Radix Sort),中序遍历trie可以得到排序。

    应用场景:

    (1) 字符串检索

    事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。

    举例:

    1,给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。

    2,给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。

    3,1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。

    (2)文本预测、自动完成,see also,拼写检查

    (3)词频统计

    1,有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。

    2,一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。

    3,寻找热门查询:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复度比较高,虽然总数是1千万,但是如果去除重复,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。

    (1) 请描述你解决这个问题的思路;

    (2) 请给出主要的处理流程,算法,以及算法的复杂度。

    ==》若无内存限制:Trie + “k-大/小根堆”(k为要找到的数目)。

    否则,先hash分段再对每一个段用hash(另一个hash函数)统计词频,再要么利用归并排序的某些特性(如partial_sort),要么利用某使用外存的方法。

    展开全文
  • MYSQL前缀索引

    2021-06-18 10:09:45
    由于Mysql支持前缀索引,所以我们可以选择将整个字段添加索引,或者只将前一部分的字符串加上索引: #整个字段 alter table T add index index1(email); #一部分字段 alter table T add index index2(email(6)); ...

    前缀索引

    1.前缀索引的优劣

    很多情况下,我们需要根据一个长字符串类型的字段去查找记录,比如身份证,邮箱,为了避免全表扫描,就需要为字符串字段添加索引。

    由于Mysql支持前缀索引,所以我们可以选择将整个字段添加索引,或者只将前一部分的字符串加上索引:

    #整个字段
    alter table T add index index1(email);
    #一部分字段
    alter table T add index index2(email(6));
    

    假设我们执行一条查询sql:

    select id,name,email from SUser where email='zhangssxyz@xxx.com';
    

    对于完整索引:

    从 index1 索引树找到满足索引值是’zhangssxyz@xxx.com’的这条记录,取得 ID2 的值;
    到主键上查到主键值是 ID2 的行,判断 email 的值是正确的,将这行记录加入结果集;
    取 index1 索引树上刚刚查到的位置的下一条记录,发现已经不满足 email='zhangssxyz@xxx.com’的条件了,循环结束。

    而对于前缀索引:

    从 index2 索引树找到满足索引值是’zhangs’的记录,找到的第一个是 ID1;
    到主键上查到主键值是 ID1 的行,判断出 email 的值不是’zhangssxyz@xxx.com’,这行记录丢弃;
    取 index2 上刚刚查到的位置的下一条记录,发现仍然是’zhangs’,取出 ID2,再到 ID 索引上取整行然后判断,这次值对了,将这行记录加入结果集;
    重复上一步,直到在 index2 上取到的值不是’zhangs’时,循环结束。
    根据这个流程,我们不难发现前缀索引有以下问题:

    索引覆盖失效:由于前缀索引在命中以后,必须再回主键索引树确定一次,所以索引覆盖对前缀索引来说是无效的。
    回表次数多:使用前缀索引后,可能会导致查询语句读数据的次数变多。

    2.如何选择合适的长度

    前缀索引需要有足够的区分度才能提高查找效率。比如有ABCC,ABDD,ABEE三条数据,选前两个个字符作为索引等于没加索引,选前三个字符作为索引就很合适。当然,实际情况肯定会更复杂,我们就需要更具体的分析。

    首先,算出这个列上有多少个不同的值:

    select count(distinct email) as L from T;
    

    依次选取不同长度的前缀来看这个值,比如我们要看一下 4~7 个字节的前缀索引,可以用这个语句:

    select 
      count(distinct left(email,4))as L4,
      count(distinct left(email,5))as L5,
      count(distinct left(email,6))as L6,
      count(distinct left(email,7))as L7,
    from T;
    

    使用前缀索引必然会损失一部分区分度,所以我们需要预先设定一个可以接受的损失比例,比如 5%。然后,在返回的 L4~L7 中,找出不小于 L * 95% 的值,然后选择最短的长度。

    3.其他优化方式

    对于邮箱,前缀索引效果还比较明显,因为@之前的字符串一般不会有太多的相似度,但是对于比如像身份证这样,同一个县市里的市民只有后几位才会有较大区别的长字符串,可能就需要设置一个非常长的前缀索引了,这显然不是我们乐意见到的。

    倒序存储

    我们可以借助reverse()函数实现倒序存储。比如身份证存入的时候我们可以倒序存储,查找的时候也先反转在查找。这样加索引以后只需要选择前几位辨识度高的即可。

    Hash字段

    我们借助crc32/64()函数去获取长字符串的校验码,在表上另外开一个字段用于存储对应的校验码,以长度较短的校验码作为索引。不过由于crc32仍然会出现值重复的情况,所以查询的时候还需要判断拿到的记录是否与条件字段完全一致。

    他们的异同如下:

    都不支持范围查找
    占用空间:倒序存储方式在主键索引上,不会消耗额外的存储空间,而 hash 字段方法需要增加一个字段。当然,倒序存储方式使用 4 个字节的前缀长度应该是不够的,如果再长一点,这个消耗跟额外这个 hash 字段也差不多抵消了。
    额外消耗:序方式每次写和读的时候,都需要额外调用一次 reverse 函数,而 hash 字段的方式需要额外调用一次 crc32() 函数。如果只从这两个函数的计算复杂度来看的话,reverse 函数额外消耗的 CPU 资源会更小些。
    查询效率:使用 hash 字段方式的查询性能相对更稳定一些。因为 crc32() 算出来的值虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数。
    当然,还有一种折中的方法,就是拆分字段:

    对于像邮箱这样的字段,有时候@后面的字段往往都是固定的几种,可以单独拆分出来作为一个字段,@前的作为单独的字段直接加全字段索引,这样减少的字段长度,并且保证也了范围查找的性能。

    展开全文
  • 上篇内容有在介绍 Gin 的路由实现时提到了前缀树,这次我们稍微深入探究一下前缀树的实现。本文以一道编程题为例,讲述前缀树的实现,以及前缀树的一种优化形态压缩前缀树。MapSum 问题LeetCode 上有一道编程题是...

    上篇内容有在介绍 Gin 的路由实现时提到了前缀树,这次我们稍微深入探究一下前缀树的实现。

    本文以一道编程题为例,讲述前缀树的实现,以及前缀树的一种优化形态压缩前缀树。

    MapSum 问题

    LeetCode 上有一道编程题是这样的

    实现一个 MapSum 类里的两个方法,insert 和 sum。

    对于方法 insert,你将得到一对(字符串,整数)的键值对。字符串表示键,整数表示值。如果键已经存在,那么原来的键值对将被替代成新的键值对。

    对于方法 sum,你将得到一个表示前缀的字符串,你需要返回所有以该前缀开头的键的值的总和。

    示例 1:

    输入: insert("apple", 3), 输出: Null

    输入: sum("ap"), 输出: 3

    输入: insert("app", 2), 输出: Null

    输入: sum("ap"), 输出: 5

    前缀树

    根据题意,我们定义的 MapSum 的数据结构为:

    type MapSum struct {

    char byte

    children map[byte]*MapSum

    val int

    }

    /** Initialize your data structure here. */

    func Constructor() MapSum {

    }

    func (this *MapSum) Insert(key string, val int) {

    }

    func (this *MapSum) Sum(prefix string) int {

    }

    假设输入数据为:

    m := Constructor()

    m.Insert("inter", 1)

    m.Insert("inner", 2)

    m.Insert("in", 2)

    m.Insert("if", 4)

    m.Insert("game", 8)

    则构造的前缀树应该是:

    bVbh7jS?w=544&h=513

    前缀树特性:

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

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

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

    Insert 函数

    Insert 函数的签名:

    func (this *MapSum) Insert(key string, val int)

    我们把 this 当做父节点,当插入的 key 长度为 1 时,则直接说明 key 对应的节点应该是 this 的孩子节点。

    if len(key) == 1 {

    for i, m := range this.children {

    // c 存在与孩子节点

    // 直接更新

    if i == c {

    m.val = val

    return

    }

    }

    // 未找到对应孩子

    // 直接生成新孩子

    this.children[c] = &MapSum{

    char: c,

    val: val,

    children: make(map[byte]*MapSum),

    }

    return

    }

    当插入的 key 长度大于 1,则寻找 key[0] 对应的子树,如果不存在,则插入新孩子节点;设置 this = this.children[key[0]] 继续迭代;

    c := key[0]

    for i, m := range this.children {

    if i == c {

    key = key[1:]

    this = m

    continue walk

    }

    }

    // 未找到节点

    this.children[c] = &MapSum{

    char: c,

    val: 0,

    children: make(map[byte]*MapSum),

    }

    this = this.children[c]

    key = key[1:]

    continue walk

    Sum 函数

    Sum 函数签名:

    func (this *MapSum) Sum(prefix string) int

    Sum 函数的基本思想为:先找到前缀 prefix 对应的节点,然后统计以该节点为树根的的子树的 val 和。

    // 先找到符合前缀的节点

    // 然后统计和

    for prefix != "" {

    c := prefix[0]

    var ok bool

    if this, ok = this.children[c]; ok {

    prefix = prefix[1:]

    continue

    } else{

    // prefix 不存在

    return 0

    }

    }

    return this.sumNode()

    sumNode 函数统计了子树的 val 和,使用递归遍历树:

    s := this.val

    for _, child := range this.children{

    s += child.sumNode()

    }

    return s

    以上是一种标准的前缀树的做法。当字符串公用的节点比较少的时候,对于每个字符都要创建单独的节点,有点浪费空间。有一种压缩前缀树的算法,在处理前缀树问题的时候能够使用更少的节点。

    压缩前缀树

    对与上面的例子来说,压缩前缀树是这样的结果:

    bVbh7jW?w=441&h=344

    对于该例子来说,明显少了很多节点。另外,我们的 MapSum 结构体也稍微有了变化:

    type MapSum struct {

    // 之前的 char byte 变成了 key string

    key string

    children map[byte]*MapSum

    val int

    }

    Insert

    压缩前缀树与前缀树的实现不同点在于节点的分裂。比如,当树中已经存在 "inner", "inter" 的情况加,再加入 "info" 时,原 "in" 节点需要分裂成 "i" -> "n" 两个节点,如图:

    bVbh7jX?w=457&h=761

    在 Insert 时,需要判断当前插入字符串 key 与 节点字符串 this.key 的最长公共前缀长度 n:

    minLen := min(len(key), len(this.key))

    // 找出最长公共前缀长度 n

    n := 0

    for n < minLen && key[n] == this.key[n] {

    n ++

    }

    然后拿 n 与 len(this.key) 比较,如果比 this.key 长度短,则 this.key 需要分裂,否则,不需要分裂。

    this 节点分裂逻辑:

    // 最前公共前缀 n < len(this.key)

    // 则该节点需要分裂

    child := &MapSum{

    val: this.val,

    key: this.key[n:],

    children: this.children,

    }

    // 更新当前节点

    this.key = this.key[:n]

    this.val = 0

    this.children = make(map[byte]*MapSum)

    this.children[child.key[0]] = child

    然后再判断 n 与 len(key),如果 n == len(key),则说明 key 对应该节点。直接更新 val

    if n == len(key) {

    this.val = val

    return

    }

    n < len(key) 时,如果有符合条件子树,则继续迭代,否则直接插入孩子节点:

    key = key[n:]

    c := key[0]

    // 如果剩余 子key 的第一个字符存在与 children

    // 则继续向下遍历树

    if a, ok := this.children[c]; ok {

    this = a

    continue walk

    } else{

    // 否则,新建节点

    this.children[c] = &MapSum{

    key: key,

    val: val,

    children: make(map[byte]*MapSum),

    }

    return

    }

    以上是压缩前缀树的做法。

    算法优化

    上述 MapSum 的 children 使用的是 map,但是 map 一般占用内存较大。可以使用 节点数组children + 节点前缀数组 indices 的方式维护子节点,其中 indices 与 children 一一对应。

    此时的结构体应该是这样的:

    type MapSum struct {

    key string

    indices []byte

    children []*MapSum

    val int

    }

    查找子树时,需要拿 key[:n][0] 与 indices 中的字符比较,找到下标后继续迭代子树;未找到时插入子树即可。

    以上。

    Y_xx

    相关内容:

    有疑问加站长微信联系(非本文作者)

    展开全文
  • 一文搞懂MySQL前缀索引

    千次阅读 2020-05-16 11:40:40
    引入 ... 假设我们在维护一个用户登录系统,用户表的定义: create table User( ID bigint unsigned primary key, email varchar(64) )engine=Innodb;...如果使用邮箱登录的话,查询语句可能这样写: ...MySQL是支持
  • 如果您对前缀范围内的重叠数做出温和的假设,则可以使用MongoDB或MySQL来最佳地进行操作.在下面的答案中,我将用MongoDB进行说明,但是将这个答案移植到MySQL应该很容易.首先,让我们重新说明一下问题.当您谈论匹配...
  • 年前项目组接微信公众号。上线之后,跟微信相关的用cid列的查询会话的SQL变慢了几十倍!思考这个问题思考了非常久。从出现以来一直是我心头的一...微信的cid会以mid-qqwanggou001为前缀插入数据explainselect *from...
  • mysql字符串前缀索引

    2021-01-27 22:03:56
    比如,这两个在 email 字段上创建索引的语句:mysql> alter table SUser add index index1(email);或mysql> alter table SUser add index index2(email(6));第一个语句创建的 index1 索引里面,包含了每个记录...
  • Mysql联合索引在B+如何存储 最左前缀匹配原则什么是联合索引在B+的存储结构最左前缀匹配原则 什么是联合索引 对多个字段同时建立的索引,也叫复合索引。 在B+的存储结构 表T: 其中c1是主键,联合索引(c2,c3...
  • MySQL最左前缀匹配原则 举个栗子: 当我们建立联合索引(a,b,c),索引文件中对应的B+每个节点都存储了a,b,c的值,而B+上查询到对应叶子节点时,是按照我们建立联合索引的字段顺序来依次查询。 即在节点中,...
  • MySQL 索引最左前缀原则

    千次阅读 2021-01-08 23:25:22
    MySQL 索引最左前缀原则 索引最佳左前缀法则:带头大哥不能死、中间兄弟不能断 1、准备数据 建表 CREATE TABLE IF NOT EXISTS staff ( id INT PRIMARY KEY auto_increment, name VARCHAR(50), age INT, pos ...
  • 最左前缀mysql的官方文档中称之为leftmost prefix,该原则适用于多列索引,想仅仅用三言两语来说清楚什么是最左前缀匹配原则不太现实,但是如果使用官方文档的一个例子来说明该原则,或许会好得多。假如现在有一张...
  • MySql最左前缀原则

    2021-01-28 04:05:40
    简单整理记录下,之前一直都没有关注过这个问题最左前缀原则:顾名思义是最左优先,以最左边的为起点任何连续的索引都能匹配上,注:如果第一个字段是范围查询需要单独建一个索引注:在创建多列索引时,要根据业务...
  • MySQL 最左前缀原则

    2021-04-06 12:28:03
    MySQL建立联合索引时会遵守最左前缀匹配原则,即最左优先,以最左边的为起点任何连续的索引都能匹配上,同时遇到范围查询(>、<、between、like)就会停止匹配。。 我先说一下,我从看来的文章里理解的内容。 ...
  • mysql> create table SUser( ID bigint unsigned primary key, email varchar(64), ... )engine=innodb 由于要使用邮箱登录, 所以业务代码中一定会出现类似于这样的语句: mysql> select f1, f2 from SUser ...
  • mysql索引结构分析

    2022-04-26 09:50:30
    MySQL在解析查询语句时会解析到这条语句是否会走到对应的索引,再去将对应的索引文件(存储在物理内存中索引文件存放路径参考MySQL索引的存放路径以及后缀文件解析)加载到RAM中,再根据索引采用的结构特性在根...
  • MySQL之前缀索引

    2022-02-11 16:44:32
    这有点类似于 Oracle 中对字段使用 Left 函数来建立函数索引,只不过 MySQL 的这个前缀索引在查询时是内部自动完成匹配的,并不需要使用 Left 函数 那么为什么不对整个字段建立索引呢?一般来说使用前缀索引,可能都...
  • MySQL-索引B+

    千次阅读 2022-01-29 23:23:12
    MySQL索引的数据结构与原理 了解索引的底层结构,B+结构 聚簇索引与非聚簇索引
  • MYSQL最左前缀原则

    2022-02-22 13:53:06
    MYSQL最左前缀原则 最左前缀原则: 概括来说:当b+的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索的,比如当(张三,20,F)这样的数据来检索的时候,b+会优先比较name...
  • mysql聚簇和非聚簇索引的区别b+和哈希索引二级索引二级索引存储主键值而不是存储行指针的优点与缺点 基础知识回顾 排序二叉树:左 < 根< 右 B :有序数组 + 多叉平衡,节点存储关键字、数据、指针; B...
  • 数据库Mysql-索引的最左前缀匹配原则 最左前缀匹配原则: 最左优先,以最左边的为起点任何连续的索引都能匹配上。同时如果范围查询(>、<、between、like)就会停止匹配。 一、例子来理解最左前缀匹配原则 前一...
  • Mysql的B+索引在单列索引上比较好理解,结构如下:那组合索引的B+存储结构是什么样的呢,为什么会有最左前缀原理,看了很多帖子找到了答案数据表B+结构b c d设置组合索引对于联合索引来说只不过比单值索引多了...
  • MySQL为了提高查询数据的效率,设计了索引,也就是b+。 b+的特点是同层的节点是双向链表连接的,目的就是为了能够在同层快速定位。 假如我们为c1、c2列建立索引,那么b+的同层节点就是根据c1、c2的大小升序...
  • Mysql联合索引的最左前缀匹配原则是面试中常问的知识点,之前也在网上看到过很多的文章,但是感觉都不够全面,所以这里就自己总结一下。2. 概念2.1 索引原理当表中有大量记录时,若要对表进行查询,第一种搜索信息...
  • 归根结底是mysql的数据库结构 B+ 在实际问题中 比如 索引index (a,b,c)有三个字段, 使用查询语句select * from table where c = '1' ,sql语句不会走index索引的 select * from table where b =‘1’ and c ='2' ...
  • MySQL之B+详解

    万次阅读 2020-05-15 12:07:56
    MySql这样的关系型数据库在查询方面有一些重要特性,是KV型的数据库或者 缓存所不具备的,比如: (1)范围查询。 (2)前缀匹配模糊查询。 (3)排序和分页。 这些特性的支持,要归功于B+这种数据结构。下面我们...
  • 文章目录索引什么是索引使用场景常见的索引哈希索引自适应哈希索引B+索引全文索引索引的使用 索引 什么是索引 在数据库中,表、数据、索引之间的关系就类似于书籍、书籍内容、书籍目录。 倘若不使用索引,则MySQL...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 32,611
精华内容 13,044
关键字:

Mysql前缀树