博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
字典树(Trie tree)
阅读量:6424 次
发布时间:2019-06-23

本文共 5426 字,大约阅读时间需要 18 分钟。

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

性质

它有3个基本性质:

  1. 不包含,除根节点外每一个节点都只包含一个。
  2. 从到某一,上经过的连接起来,为该对应的。
  3. 每个的所有包含的都不相同。

[]图示

这是一个Trie结构的例子: 

在这个Trie结构中,保存了A、to、tea、ted、ten、i、in、inn这8个字符串,仅占用8个(不包括占用的空间)。

实例

这是一个用于词频统计的程序范例,因使用了getline(3),所以需要才能链接成功,没有的话可以自行改写。代码由捐献,遵从GPL版权声明。

#include 
#include
#include
#define TREE_WIDTH 256 #define WORDLENMAX 128 struct trie_node_st { int count; struct trie_node_st *next[TREE_WIDTH];}; static struct trie_node_st root={0, {NULL}}; static char *spaces=" \t\n/.\"\'()"; static intinsert(const char *word){ int i; struct trie_node_st *curr, *newnode; if (word[0]=='\0') { return 0; } curr = &root; for (i=0; ; ++i) { if (curr->next[ word[i] ] == NULL) { newnode=(struct trie_node_st*)malloc(sizeof(struct trie_node_st)); memset(newnode, 0, sizeof(struct trie_node_st)); curr->next[ word[i] ] = newnode; } if (word[i] == '\0') { break; } curr = curr->next[ word[i] ]; } curr->count ++; return 0;} static voidprintword(const char *str, int n){ printf("%s\t%d\n", str, n);} static intdo_travel(struct trie_node_st *rootp){ static char worddump[WORDLENMAX+1]; static int pos=0; int i; if (rootp == NULL) { return 0; } if (rootp->count) { worddump[pos]='\0'; printword(worddump, rootp->count); } for (i=0;i
next[i]); pos--; } return 0;} intmain(void){ char *linebuf=NULL, *line, *word; size_t bufsize=0; int ret; while (1) { ret=getline(&linebuf, &bufsize, stdin); if (ret==-1) { break; } line=linebuf; while (1) { word = strsep(&line, spaces); if (word==NULL) { break; } if (word[0]=='\0') { continue; } insert(word); } } /* free(linebuf); */ do_travel(&root); exit(0);}

在给一个例子:

#define MAX_NUM 26enum NODE_TYPE{ //"COMPLETED" means a string is generated so far.  COMPLETED,  UNCOMPLETED};struct Node {  enum NODE_TYPE type;  char ch;  struct Node* child[MAX_NUM]; //26-tree->a, b ,c, .....z}; struct Node* ROOT; //tree root struct Node* createNewNode(char ch){  // create a new node  struct Node *new_node = (struct Node*)malloc(sizeof(struct Node));  new_node->ch = ch;  new_node->type == UNCOMPLETED;  int i;  for(i = 0; i < MAX_NUM; i++)    new_node->child[i] = NULL;  return new_node;} void initialization() {//intiazation: creat an empty tree, with only a ROOTROOT = createNewNode(' ');} int charToindex(char ch) { //a "char" maps to an index
return ch - 'a';} int find(const char chars[], int len) { struct Node* ptr = ROOT; int i = 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);} void insert(const char chars[], int len) { struct Node* ptr = ROOT; int i; 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;}

Trie树的基本实现

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

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

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

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

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

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:

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

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

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

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

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

7)刚刚分享了:"算法合集之《浅析字母树在信息学竞赛中的应用》.pdf" 下载链接: 

转载地址:http://lnwga.baihongyu.com/

你可能感兴趣的文章
又一款开源手机要来了 —— WiPhone
查看>>
跨越鸿沟——工业大数据的实践与思考
查看>>
DBA和开发同事的一些代沟(五)
查看>>
【OGG】关于在一套复制环境中使用不同版本OGG的问题
查看>>
大咖丨交通运输部科学研究院:交通运输大数据的基础环境正日益成熟-清数•思享会...
查看>>
【中亦安图】导致Oracle性能抖动的参数提醒(4)
查看>>
jsp中forward和redirect的区别(转)
查看>>
TOUGHRADIUS 抛弃 AGPL,采用 Apache 协议
查看>>
《CUDA C编程权威指南》——3.4节避免分支分化
查看>>
《日志管理与分析权威指南》一2.2 日志的概念
查看>>
《Adobe After Effects CC 经典教程(彩色版)》——1.5 对合成图像做动画处理
查看>>
《数据结构与算法:Python语言描述》一1.3算法和算法分析
查看>>
python异步并发模块concurrent.futures简析
查看>>
【JAVA秒会技术之秒杀面试官】JavaEE常见面试题(五)
查看>>
《ZooKeeper:分布式过程协同技术详解》——1.2 示例:主-从应用
查看>>
webview与js交互
查看>>
阿里云Redis集群子实例内存查看
查看>>
《JavaScript启示录》——1.6 从构造函数创建字面量值
查看>>
通过R让你的复杂网络图更具艺术感
查看>>
《数字图像处理与机器视觉——Visual C++与Matlab实现》导读
查看>>