字典树Trie树

一、字典树

字典树——Trie树,又称为前缀树(Prefix Tree)、单词查找树或键树,是一种多叉树结构。

上图是一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。从上图可以归纳出Trie树的基本性质:

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

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

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

* 通常在实现的时候,会在结点结构中设置一个标志,用来标记该节点处是否构成一个单词(关键字)。

* 可以看出,Trie树的关键字一般都是字符串,而且Trie树把每个关键字保存在一条路径上,而不是一个节点中。另外,有两个公共前缀的关键字,在Trie树种前缀部分的路径相同。所以Trie又称为前缀树。

二、字典树的优缺点

优点

  1. 插入和查询的效率很高,均是O(m),其中m是待插入/查询的字符串长度。

    • 关于查询,有人会说hash表时间复杂度是O(1)不是更快?但是哈希搜索的效率取决于哈希函数的好坏,若一个坏的hash函数导致了很多冲突,效率不一定比Trie树高
  2. Trie树中不同的关键字不会产生冲突。
  3. Trie树中只有在允许一个关键字关联多个值的情况下才有类似hash碰撞发生。
  4. Trie树不用求hash值,对短字符串有更快的速度。通常,求hash值也是需要遍历字符串的。
  5. Trie树可以对关键字按照字典序排序。
    • 字典排序(lexicographical order)是一种对于随机变量形成序列的排序方法。其方法是,按照字母顺序,或者数字小大顺序,由小到大的形成序列。
  6. 每一颗Trie树都可以被看做一个简单版的确定有限状态的自动机(DFA,deterministic finite automation),也就是说,对于一个任意给定属于该自动机的状态(①)和一个属于该自动机字母表的字符(②),都可以根据给定的转移函数(③)转到下一个状态。其中:
    • ① 对于Trie树的每一个节点都确定一个自动机的状态。
    • ② 给定一个属于该自动机字母表的字符,在图中可以看到根据不同字符形成的分支;
    • ③ 从当前节点进入下一层次节点的过程进过状态转移函数得出。

    核心思想是:空间换时间,利用字符串的公共前缀来减少无谓的字符串比较以达到提高查询效率的目的。

缺点

  1. 当hash函数很好时,Trie树的查找效率低于哈希搜索。
  2. 空间消耗大。

三、Trie树的应用

  1. 字符串检索

    检索、查询功能是Trie树最原始功能,思路就是从根节点开始一个一个字符进行比较。

    • 如果沿路比较,发现不同的字符,则表示该字符串在集合中不存在。
    • 如果所有的字符全部比较并且完全相同,还需要判断最后一个节点标识位(标记该节点是否为一个关键字)。
  2. 词频统计

    Trie树常被搜索引擎用于文本词频统计。

    思路:为了实现词频统计,我们修改了节点结构,用一个整型变量count来计数。对每一个关键字执行插入操作,若已存在,计数加1,若不存在,插入后count置 1。

    (1. 2. 都可以用hash table做)

  3. 字符串排序

    Trie树可以对大量字符串按字典序进行排序,思路也很简单:遍历一次所有关键字,将它们全部插入trie树,树的每个结点的所有儿子很显然地按照字母表排序,然后先序遍历输出Trie树中所有关键字即可。

  4. 前缀匹配

    例如:找出一个字符串集合中所有以ab开头的字符串。我们只需要用所有字符串构造一个trie树,然后输出以a->b->开头的路径上的关键字即可。 trie树前缀匹配常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。

  5. 作为辅助结构

    如后缀树,AC自动机

    有穷自动机 参考资料:http://blog.csdn.net/yukuninfoaxiom/article/details/6057736

  6. 与哈希表相比

    优点:

    • trie数据查找与不完美哈希表(链表实现)在最坏情况下更快;对于trie树,最差为O(m),m为查找字符串的长度;对于不完美哈希表,会有键值冲突(不同键哈希相同),最坏为O(N),N为全部字符产生的个数。典型情况是O(m)用于哈希计算,O(1)用于数据查找。
    • trie中不同键没有冲突
    • trie的桶与哈希表用于存储键冲突的桶类似,仅在单个键与多个值关联时需要
    • 当更多的键加入到trie中,无需提供hash方法或改变hash方法
    • trie通过键为条目提供字母顺序

      缺点:

    • trie数据查找在某些情况下(磁盘或随机访问时间远远高于主存)比哈希表慢
    • 当键值为某些类型(如浮点型),前缀链很长且前缀不是特别有意义。
    • 一些trie会比hash表更消耗内存。对于trie,每个字符串的每个字符都要分配内存;对于大多数hash,只需要为整个条目分配一块内存。
  7. 与二叉搜索树相比

    二叉搜索树,又称二叉排序树,它满足:

    • 任意节点如果左子树不为空,左子树所有节点的值都小于根节点的值;
    • 任意节点如果右子树不为空,右子树所有节点的值都大于根节点的值;
    • 左右子树也都是二叉搜索树;
    • 所有节点的值都不相同。

    其实二叉搜索树的优势已经在与查找、插入的时间复杂度上了,通常只有O(log n),很多集合都是通过它来实现的。在进行插入的时候,实质上是给树添加新的叶子节点,避免了节点移动,搜索、插入和删除的复杂度等于树的高度,属于O(log n),最坏情况下整棵树所有的节点都只有一个子节点,完全变成一个线性表,复杂度是O(n)。

    Trie树在最坏情况下查找要快过二叉搜索树,如果搜索字符串长度用m来表示的话,它只有O(m),通常情况(树的节点个数要远大于搜索字符串的长度)下要远小于O(n)。

四、实现


#include <iostream>

#include <string>

using namespace std;

#define ALPHABET_SIZE 26

typedef struct trie_node
{
    int count;   // 记录该节点代表的单词的个数
    trie_node *children[ALPHABET_SIZE]; // 各个子节点
}*trie;

trie_node* create_trie_node()
{
    trie_node* pNode = new trie_node();
    pNode->count = 0;
    for(int i=0; i<ALPHABET_SIZE; ++i)
        pNode->children[i] = NULL;
    return pNode;
}

void trie_insert(trie root, char* key)
{
    trie_node* node = root;
    char* p = key;
    while(*p)
    {
        if(node->children[*p-‘a‘] == NULL)
        {
            node->children[*p-‘a‘] = create_trie_node();
        }
        node = node->children[*p-‘a‘];
        ++p;
    }
    node->count += 1;
}

/**
 * 查询:不存在返回0,存在返回出现的次数
 */

int trie_search(trie root, char* key)
{
    trie_node* node = root;
    char* p = key;
    while(*p && node!=NULL)
    {
        node = node->children[*p-‘a‘];
        ++p;
    }

    if(node == NULL)
        return 0;
    else

                return node->count;

}

int main()
{
    // 关键字集合

        char keys[][8] = {"the", "a", "there", "answer", "any", "by", "bye", "their"};

    trie root = create_trie_node();

    // 创建trie树

        for(int i = 0; i < 8; i++)

        trie_insert(root, keys[i]);

    // 检索字符串

        char s[][32] = {"Present in trie", "Not present in trie"};

    printf("%s --- %s\n", "the", trie_search(root, "the")>0?s[0]:s[1]);
    printf("%s --- %s\n", "these", trie_search(root, "these")>0?s[0]:s[1]);
    printf("%s --- %s\n", "their", trie_search(root, "their")>0?s[0]:s[1]);
    printf("%s --- %s\n", "thaw", trie_search(root, "thaw")>0?s[0]:s[1]);

    return 0;
}

对于Trie树,我们一般只实现插入和搜索操作。这段代码可以用来检索单词和统计词频。

五、Trie树改进

  1. 按位树(Btiwise Trie):原理上和普通Trie树差不多,只不过普通Trie树存储的最小单位是字符,但是Bitwise Trie存放的是位而已。位数据的存取由CPU指令一次直接实现,对于二进制数据,它理论上要比普通Trie树快。
  2. 节点压缩
    • ①分支压缩: 对于稳定的Trie树,基本上都是查找和读取的操作,完全可以把一些分支进行压缩。例如,下图中最右侧分支inn可以直接压缩成一个节点“inn”,而不需要作为一个常规子树存在。Radix树就是根据这个原理来解决Trie树过深的问题。

    • ②节点映射表:这种方式也是Trie树节点可能几乎完全确定下采用的,针对Trie树节点的每一个状态,如果状态总数重复很多的话,通过一个元素为数字的多维数组(比如Triple Array Trie)来表示,这样存储Trie树本身的空间开销会小一些,虽然引入了额外的映射表。
  3. 双数组TRIE树(Double Array Trie)

    它在保证Trie树检索速度的前提下,提高空间利用率而提出的一种数据结构,本质上还是一个确定有限自动机。(所谓DFA就是一个能够实现状态专一的自动机,对于一个给定的属于该自动机的状态和一个数据该自动机字符表Σ的字符,它能够根据预先给定的状态转移函数转移到下一个状态。)

    对于DAT来说,每个节点代表自动机的一个状态, 根据变量的不同,进行状态转移,当达到结束状态或者无法转移时,完成查询。

    参考资料:http://blog.csdn.net/zzran/article/details/8462002

六、Trie树的其他形式

上图主要说明下这些算法数据结构之间的关系。图中黄色部分主要写明了这些算法和数据结构的一些关键点。

图中可以看到这样一些关系:extend-kmp 是kmp的扩展;ac自动机是kmp的多串形式;它是一个有限自动机;而trie图实际上是一个确定性有限自动机;ac自动机,trie图,后缀树实际上都是一种trie;后缀数组和后缀树都是与字符串的后缀集合有关的数据结构;trie图中的后缀指针和后缀树中的后缀链接这两个概念及其一致。

七、Trie树的性能比较

参考博客 http://www.hankcs.com/nlp/performance-comparison-of-several-trie-tree.html

参考资料

  1. Trie树
  2. Trie树
  3. BitWise Trie
  4. AC自动机
时间: 2024-10-19 19:25:13

字典树Trie树的相关文章

[POJ] #1003# 487-3279 : 桶排序/字典树(Trie树)/快速排序

一. 题目 487-3279 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 274040   Accepted: 48891 Description Businesses like to have memorable telephone numbers. One way to make a telephone number memorable is to have it spell a memorable word or

Atitit 常见的树形结构 红黑树 &#160;二叉树 &#160;&#160;B树 B+树 &#160;Trie树&#160;attilax理解与总结

Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树2 1.4. 满二叉树和完全二叉树..完全二叉树说明深度达到完全了.2 1.5. 属的逻辑表示 树形比奥死,括号表示,文氏图,凹镜法表示3 1.6. 二叉树是数据结构中一种重要的数据结构,也是树表家族最为基础的结构.3 1.6.1. 3.2 平衡二叉

[转载]字典树(trie树)、后缀树

(1)字典树(Trie树) Trie是个简单但实用的数据结构,通常用于实现字典查询.我们做即时响应用户输入的AJAX搜索框时,就是Trie开始.本质上,Trie是一颗存储多个字符串的树.相邻节点间的边代表一个字符,这样树的每条分支代表一则子串,而树的叶节点则代表完整的字符串.和普通树不同的地方是,相同的字符串前缀共享同一条分支.还是例子最清楚.给出一组单词,inn, int, at, age, adv, ant, 我们可以得到下面的Trie: 可以看出: 每条边对应一个字母. 每个节点对应一项前

【数据结构】字典树/Trie树/前缀树 - 字符串的统计、排序和保存

字典树 描述 字典树,又称单词查找树.Trie树.前缀树,是一种树形结构,是一种哈希树的变种. 典型应用是用于统计.排序和保存大量的字符串(但不仅限于字符串). 常见操作有插入和查找,删除操作少见. 性质 根节点不包含字符 除根节点外每一个节点都只包含一个字符 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串 每个节点的所有子节点包含的字符都不相同 优点 利用字符串的公共前缀来减少查询时间 最大限度地减少无谓的字符串比较 查询效率比哈希树高 自带字典序排序 直接判断重复,或者记

树-trie树

字典树(trie树) (图f) 字典树是一种以树形结构保存大量字符串.以便于字符串的统计和查找,经常被搜索引擎系统用于文本词频统计.它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高.具有以下特点(图f):(1)根节点为空:(2)除根节点外,每个节点包含一个字符:(3)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串.(4)每个字符串在建立字典树的过程中都要加上一个区分的结束符,避免某个短字符串正好是某个长字符串的前缀而淹没. T

【BZOJ3217】ALOEXT 替罪羊树+Trie树

[BZOJ3217]ALOEXT Description taorunz平时最喜欢的东西就是可移动存储器了……只要看到别人的可移动存储器,他总是用尽一切办法把它里面的东西弄到手. 突然有一天,taorunz来到了一个密室,里面放着一排可移动存储器,存储器里有非常珍贵的OI资料……不过比较特殊的是,每个存储器上都写着一个非负整数.taorunz很高兴,要把所有的存储器都拿走(taorunz的智商高达500,他一旦弄走了这里的所有存储器,在不久到来的AHOI和NOI中……你懂的).不过这时有一个声音

HiHo1014 : Trie树(Trie树模板题)

描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一本词典,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能对于每一个我给出的字符串,都在这个词典里面找到以这个字符串开头的所有单词呢?” 身经百战的小Ho答道:“怎么会不能呢!你每给我一个字符串,我就依次遍历词典里的所有单词,检查你给我的字符串是不是这个单词的前缀不就是了?” 小Hi笑道:“你啊,还是太年轻了!~假设这本词典里有10万个单

剑指Offer——Trie树(字典树)

剑指Offer--Trie树(字典树) Trie树 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高. Trie的核心思想是空间换时间.利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的. Trie树也有它的缺点,Trie树的内存消耗非常大.当然,或许用左儿子右兄弟的方法建树的话,可能会好点.可见,优

【转】B树、B-树、B+树、B*树、红黑树、 二叉排序树、trie树Double Array 字典查找树简介

B  树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中:否则,如果查询关键字比结点关键字小,就进入左儿子:如果比结点关键字大,就进入右儿子:如果左儿子或右儿子的指针为空,则报告找不到相应的关键字: 如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树的搜索性