Trie树的数组实现原理

Trie(Retrieval Tree)又称前缀树,可以用来保存多个字符串,并且非常便于查找。在trie中查找一个字符串的时间只取决于组成该串的字符数,与树的节点数无关。因此,它的查找速度通常比二叉搜索树更快。trie的结构很简单,每条边表示一个字符,从根节点到叶节点就可以表示一个完整的字符串。所以,如果用trie表示一组英文单词,就是一颗26叉数;表示一组自然数,就是一颗10叉树。直观上,实现trie很简单,比如实现英文单词的trie,使用如下的节点构造树:

:::c
struct node
{
    char chr;
    struct node *edges[26];
};

这样做虽然简单,但没有很好的利用内存,edges数组肯定很多都是闲置的,如果使用到更多字符的话,这种浪费会更严重。这里介绍一种基于数组结构的trie实现方式,不仅节省内存,而且查询速度更快。基于数组查表的时间复杂度为O(|P|),基于平衡树的时间复杂度为O(|P|log|Σ|),其中,P表示查询的字符串长度,Σ表示字符集合。

基于数组的实现方式,把trie看作一个DFA,树的每个节点对应一个DFA状态,每条从父节点指向子节点的有向边对应一个DFA变换。遍历从根节点开始,字符串的每个字符作为输入用来确定下一个状态,直到叶节点。

三数组trie

trie可以用三个数组来表示:

  • base: 其中的每个元素对应trie上的一个节点,即DFA的状态。对于节点s,base[s]nextcheck在状态转换表中的起始位置。如果base[i]为负值或没有next转换,表示该状态为一个词语。
  • next: 和check搭配使用,提供数据池分配稀疏向量,用于保存trie状态转换表的各行数据。来自各个节点的转换向量保存在此数组中。
  • check: 与next平行使用,它与next相同位置的元素记录了next中对应元素的拥有者,即之前的状态。

所谓trie*状态转换表,即状态转换矩阵,是DFA里的概念:横行是状态转换向量*,比如,状态s接受n种输入字符c1,...,cn,即构成状态s的状态转换向量;纵列是各种状态,即trie的各节点。

对于输入字符c,从状态s转换到t,用三数组trie可以表示为:

check[base[s]+c] = s
next[base[s]+c] = t

类似下图:

遍历树

对于给定状态s和输入字符c的遍历算法表示如下:

t := base[s]+c
if check[t] = s then
    next state := next[t]
else
    fail
endif

创建树

当插入一个状态转换,比如,输入字符c,状态从s转换到t,此时,数组元素next[base[s]+c]]应该是空的,否则,整个占用该数组元素位置的状态转换向量或者状态s的状态转换向量必须要重新迁移(relocate)。实际过程中选择代价较小的那个。假设迁移状态s的状态转换向量,重新分配的起始位置为b,整个过程很简单:

Relocate(s: 状态, b: next数组中新的起始位置)
begin
    foreach 状态s后的每种输入字符c
    begin
        check[b+c] := s  标记前件状态
        next[b+c] := next[base[s]+c]   复制原先的状态数据
        check[base[s]+c] := none 释放原先的状态数据
    end
    base[s] := b  完成迁移
end

新位置b的选择比较关键,应该避免迁移过程中再次发生冲突。整个过程如下图,实线表示迁移前,虚线表示迁移后:

双数组trie

三数组trie的nextcheck数组元素之间存在间隙,可以将basenext合并,把base数组中的表示穿插在next中进行,而next中有值的项直接表示为base的内容,这样就得到两个平行的数组basecheck,即双数组trie。

对于输入字符c,从状态s转换到t,用双数组trie可以表示为:

check[base[s]+c] = s
base[s]+c =t

类似下图

遍历

对于给定状态s和输入字符c的遍历算法表示如下:

t := base[s] + c;
if check[t] = s then
    next state := t
else
    fail
endif

创建树

双数组trie的创建类似三数组trie,但重新迁移方法略有不同:

Relocate(s: 状态, s: base数组中的起始位置)
begin
    foreach 状态s后的每种输入字符c
    begin
        check[b+c] := s   标记前件状态
        base[b+c] := base[base[s}+c]  复制原先的状态数据
        foreach 状态base[s]+c后的每种输入字符d
        begin
            check[base[base[s]+c]+d] := b+c
        end
        check[base[s]+c] := none  释放原先的状态数据
    end
    base[s] := b 完成迁移
end

整个过程如下图:

参考

http://blog.jqian.net/post/trie.html

时间: 2024-08-06 04:18:50

Trie树的数组实现原理的相关文章

树状数组的原理和基础应用

这样的数据结构称作树状数组,它支持O(logN)的单点修改和区间查询,效率高并且代码简洁,缺点在于适用范围不如线段树广.不难看出(雾),tree[i]表示a[i]及之前的 lowbit(i)个 数,定义lowbit(i)等于取i的二进制中最后一个'1'表示的大小观察发现(.),修改a[i]只需更新包含i的节点,倒推可知从i开始,每次把i加上lowbit(i),这些节点包含了a[i]从i开始,每次把i减去lowbit(i)直到为0,这些节点的值加起来就是前缀和.例题1:树状数组1  https:/

POJ - 2155 Matrix (二维树状数组 + 区间改动 + 单点求值 或者 二维线段树 + 区间更新 + 单点求值)

POJ - 2155 Matrix Time Limit: 3000MS   Memory Limit: 65536KB   64bit IO Format: %I64d & %I64u Submit Status Description Given an N*N matrix A, whose elements are either 0 or 1. A[i, j] means the number in the i-th row and j-th column. Initially we ha

HDU_2642_二维树状数组

Stars Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/65536 K (Java/Others)Total Submission(s): 1628    Accepted Submission(s): 683 Problem Description Yifenfei is a romantic guy and he likes to count the stars in the sky.To make the pr

Day2:T4求逆序对(树状数组+归并排序)

T4: 求逆序对 A[I]为前缀和 推导 (A[J]-A[I])/(J-I)>=M A[j]-A[I]>=M(J-I) A[J]-M*J>=A[I]-M*I 设B[]=A[]-M*(); B[J]>=B[I] 也就是求逆序对: 求逆序对的方法主要有两种: 归并排序: 树状数组: 这里两种方法都学习一下: 1.之前对于树状数组的印象就只有单点修改和区间求和 一直觉得lowbit是一个神奇的东西(至今没有搞懂原理) 上网搜了一下用树状数组求逆序对的方法,发现有一个大神写的很棒....看

HDU 3584 Cube(三维树状数组)

Cube Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others) Total Submission(s): 1660    Accepted Submission(s): 865 Problem Description Given an N*N*N cube A, whose elements are either 0 or 1. A[i, j, k] means the numbe

ST表与树状数组

ST表  st表可以解决区间最值的问题.可以做到O(nlogn)预处理 ,O(1)查询,但是不支持修改. st表的大概思路就是用st[i][j]来表示从i开始的2的j次方个树中的最值,查询时就从左端点开始,找到区间长度是2的多少次方,然后进行查询.然而,很明显,我们要查询的区间长度不一定是2的多少次幂.那怎么做到O(1)查询呢,这就要用到最值的特性. 如图,假如我们要查询2到7之间的最大值,但是7-2+1在22与23之间,我们选择22,也就是st[2][2],那剩下的6,7怎么办,我们考虑倒着从

树状数组review学习

学习参考: https://blog.csdn.net/flushhip/article/details/79165701 https://blog.csdn.net/moep0/article/details/52770728 树状数组在寒假集训的时候其实有讲过,但是当时只是有了板子,没有很深地去学习理解,现在回来想去理解一下这个很好用的数据结构. 树状数组的作用 树状数组是对于一个用来处理动态更新.动态统计区间问题的一种良好的数据结构,查询和修改复杂度都为O(logn)的数据结构. 树状数组

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

AVL树,红黑树,B-B+树,Trie树原理和应用

前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作(如插入删除等等) 目录 AVL树 AVL树原理与应用 红黑树 红黑树原理与应用 B/B+树 B/B+树原理与应用 Trie树 Trie树原理与应用 AVL树 简介: AVL树是最早的自平衡二叉树,在早期应用还相对来说比较广,后期由于旋转次数过多而被红黑树等结构取代(二者都是用来搜索的),AVL树内