zoukankan      html  css  js  c++  java
  • 数据结构之Trie树

    1、 概述

    Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树。

    Trie一词来自retrieve,发音为/tri:/ “tree”,也有人读为/traɪ/ “try”。

    Trie树可以利用字符串的公共前缀来节约存储空间。如下图所示,该trie树用10个节点保存了6个字符串tea,ten,to,in,inn,int:

    在该trie树中,字符串in,inn和int的公共前缀是“in”,因此可以只存储一份“in”以节省空间。当然,如果系统中存在大量字符串且这些字符串基本没有公共前缀,则相应的trie树将非常消耗内存,这也是trie树的一个缺点。

    Trie树的基本性质可以归纳为:

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

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

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

    2、 Trie树的基本实现

    字母树的插入(Insert)、删除( Delete)和查找(Find)都非常简单,用一个一重循环即可,即第i 次循环找到前i 个字母所对应的子树,然后进行相应的操作。实现这棵字母树,我们用最常见的数组保存(静态开辟内存)即可,当然也可以开动态的指针类型(动态开辟内存)。至于结点对儿子的指向,一般有三种方法:

    1、对每个结点开一个字母集大小的数组,对应的下标是儿子所表示的字母,内容则是这个儿子对应在大数组上的位置,即标号;

    2、对每个结点挂一个链表,按一定顺序记录每个儿子是谁;

    3、使用左儿子右兄弟表示法记录这棵树。

    三种方法,各有特点。第一种易实现,但实际的空间要求较大;第二种,较易实现,空间要求相对较小,但比较费时;第三种,空间要求最小,但相对费时且不易写。

    下面给出动态开辟内存的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #define MAX_NUM 26
    enumNODE_TYPE{ //"COMPLETED" means a string is generated so far.
      COMPLETED,
      UNCOMPLETED
    };
    structNode {
      enumNODE_TYPE type;
      charch;
      structNode* child[MAX_NUM]; //26-tree->a, b ,c, .....z
    };
     
    structNode* ROOT; //tree root
     
    structNode* createNewNode(charch){
      // create a new node
      structNode *new_node = (structNode*)malloc(sizeof(structNode));
      new_node->ch = ch;
      new_node->type == UNCOMPLETED;
      inti;
      for(i = 0; i < MAX_NUM; i++)
        new_node->child[i] = NULL;
      returnnew_node;
    }
     
    voidinitialization() {
    //intiazation: creat an empty tree, with only a ROOT
    ROOT = createNewNode(' ');
    }
     
    intcharToindex(charch) { //a "char" maps to an index<br>
    returnch - 'a';
    }
     
    intfind(constchar chars[], intlen) {
      structNode* ptr = ROOT;
      inti = 0;
      while(i < len) {
       if(ptr->child[charToindex(chars[i])] == NULL) {
       break;
      }
      ptr = ptr->child[charToindex(chars[i])];
      i++;
      }
      return(i == len) && (ptr->type == COMPLETED);
    }
     
    voidinsert(constchar chars[], intlen) {
      structNode* ptr = ROOT;
      inti;
      for(i = 0; i < len; i++) {
       if(ptr->child[charToindex(chars[i])] == NULL) {
        ptr->child[charToindex(chars[i])] = createNewNode(chars[i]);
      }
      ptr = ptr->child[charToindex(chars[i])];
    }
      ptr->type = COMPLETED;
    }

    3、 Trie树的高级实现

    可以采用双数组(Double-Array)实现。利用双数组可以大大减小内存使用量,具体实现细节见参考资料(5)(6)。

    4、 Trie树的应用

    Trie是一种非常简单高效的数据结构,但有大量的应用实例。

    (1) 字符串检索

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

    举例:

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

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

    (2)字符串最长公共前缀

    Trie树利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。

    举例:

    @ 给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少?

    解决方案:首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线(Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。

    而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:

    1. 利用并查集(Disjoint Set),可以采用采用经典的Tarjan 算法

    2. 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;

    (关于并查集,Tarjan算法,RMQ问题,网上有很多资料。)

    (3)排序

    Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。

    举例:

    @ 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。

    (4) 作为其他数据结构和算法的辅助结构

    如后缀树,AC自动机等

    5、 Trie树复杂度分析

    (1) 插入、查找的时间复杂度均为O(N),其中N为字符串长度。

    (2) 空间复杂度是26^n级别的,非常庞大(可采用双数组实现改善)。

    6、 总结

    Trie树是一种非常重要的数据结构,它在信息检索,字符串匹配等领域有广泛的应用,同时,它也是很多算法和复杂数据结构的基础,如后缀树,AC自动机等,因此,掌握Trie树这种数据结构,对于一名IT人员,显得非常基础且必要!

    7、 参考资料

    (1)wiki:http://en.wikipedia.org/wiki/Trie

    (2) 博文《字典树的简介及实现》:

    http://hi.baidu.com/luyade1987/blog/item/2667811631106657f2de320a.html

    (3) 论文《浅析字母树在信息学竞赛中的应用》

    (4)  论文《Trie图的构建、活用与改进》

    (5)  博文《An Implementation of Double-Array Trie》:

    http://linux.thai.net/~thep/datrie/datrie.html

    (6) 论文《An Efficient Implementation of Trie Structures》:

    http://www.google.com.hk/url?sa=t&source=web&cd=4&ved=0CDEQFjAD&url=http%3A%2F%2Fciteseerx.ist.psu.edu%2Fviewdoc%2Fdownload%3Fdoi%3D10.1.1.14.8665%26rep%3Drep1%26type%3Dpdf&ei=qaehTZiyJ4u3cYuR_O4B&usg=AFQjCNF5icQbRO8_WKRd5lMh-eWFIty_fQ&sig2=xfqSGYHBKqOLXjdONIQNVw

    ————————————————————————————————————-

    更多关于数据结构和算法的介绍,请查看:数据结构与算法汇总

    ————————————————————————————————————-

    原创文章,转载请注明: 转载自董的博客

    本文链接地址: http://dongxicheng.org/structure/trietree/

  • 相关阅读:
    POJ 1659 Frogs' Neighborhood
    zoj 2913 Bus Pass(BFS)
    ZOJ 1008 Gnome Tetravex(DFS)
    POJ 1562 Oil Deposits (DFS)
    zoj 2165 Red and Black (DFs)poj 1979
    hdu 3954 Level up
    sgu 249 Matrix
    hdu 4417 Super Mario
    SPOJ (BNUOJ) LCM Sum
    hdu 2665 Kth number 划分树
  • 原文地址:https://www.cnblogs.com/KingIceMou/p/7001008.html
Copyright © 2011-2022 走看看