Trie树:应用于统计和排序

1. 什么是trie树

1.Trie树 (特例结构树)

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

2.  三个基本特性:  

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

3 .例子

和二叉查找树不同,在trie树中,每个结点上并非存储一个元素。
       trie树把要查找的关键词看作一个字符序列。并根据构成关键词字符的先后顺序构造用于检索的树结构。
       在trie树上进行检索类似于查阅英语词典。

      一棵m度的trie树或者为空,或者由m棵m度的trie树构成。

例如,电子英文词典,为了方便用户快速检索英语单词,可以建立一棵trie树。例如词典由下面的单词成:a、b、c、aa、ab、ac、ba、ca、aba、abc、baa、bab、bac、cab、abba、baba、caba、abaca、caaba

再举一个例子。给出一组单词,inn, int, at, age, adv, ant, 我们可以得到下面的Trie:

可以看出:

  • 每条边对应一个字母。
  • 每个节点对应一项前缀。叶节点对应最长前缀,即单词本身。
  • 单词inn与单词int有共同的前缀“in”, 因此他们共享左边的一条分支,root->i->in。同理,ate, age, adv, 和ant共享前缀"a",所以他们共享从根节点到节点"a"的边。

查询操纵非常简单。比如要查找int,顺着路径i -> in -> int就找到了。

2. trie树的实现

1.插入过程

对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入trie树。

2. 查找过程

其方法为:

(1) 从根结点开始一次搜索;

(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;

(3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
(4) 迭代过程……
(5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。其他操作类似处理.

即从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。如下图中:trie树中存在的就是abc、d、da、dda四个单词。在实际的问题中可以将标记颜色的标志位改为数量count等其他符合题目要求的变量。

代码:
  1. // stdafx.h : include file for standard system include files,
  2. // or project specific include files that are used frequently, but
  3. // are changed infrequently
  4. //
  5. #pragma once
  6. #include <stdio.h>
  7. #include "stdlib.h"
  8. #include <iostream>
  9. #include <string.h>
  10. using namespace std;
  11. //宏定义
  12. #define TRUE   1
  13. #define FALSE   0
  14. #define NULL 0
  15. #define OK    1
  16. #define ERROR   0
  17. #define INFEASIBLE -1
  18. #define OVERFLOW -2
  19. const int num_chars = 26;
  20. class Trie {
  21. public:
  22. Trie();
  23. Trie(Trie& tr);
  24. virtual ~Trie();
  25. int trie_search(const char* word, char* entry ) const;
  26. int insert(const char* word, const char* entry);
  27. int remove(const char* word, char* entry);
  28. protected:
  29. struct Trie_node{
  30. char* data; //若不为空,表示从root到此结点构成一个单词
  31. Trie_node* branch[num_chars]; //分支
  32. Trie_node(); //构造函数
  33. };
  34. Trie_node* root; //根结点(指针)
  35. };
  1. // Test.cpp : Defines the entry point for the console application.
  2. //
  3. #include "stdafx.h"
  4. Trie::Trie_node::Trie_node() {
  5. data = NULL;
  6. for (int i=0; i<num_chars; ++i)
  7. branch[i] = NULL;
  8. }
  9. Trie::Trie():root(NULL) {}
  10. Trie::~Trie(){}
  11. int Trie::trie_search(const char* word, char* entry ) const {
  12. int position = 0;  //层数
  13. char char_code;
  14. Trie_node *location = root;  //从根结点开始
  15. while( location!=NULL && *word!=0 ) {
  16. if (*word >= 'A' && *word <= 'Z')
  17. char_code = *word-'A';
  18. else if (*word>='a' && *word<='z')
  19. char_code = *word-'a';
  20. else return 0;// 不合法的单词
  21. //转入相应分支指针
  22. location = location->branch[char_code];
  23. position++;
  24. word++;
  25. }
  26. //找到,获取数据,成功返回
  27. if ( location != NULL && location->data != NULL ) {
  28. strcpy(entry,location->data);
  29. return 1;
  30. }
  31. else  return 0;// 不合法的单词
  32. }
  33. int Trie::insert(const char* word, const char* entry) {
  34. int result = 1, position = 0;
  35. if ( root == NULL ) root = new Trie_node;   //初始插入,根结点为空
  36. char char_code;
  37. Trie_node *location = root;   //从根结点开始
  38. while( location!=NULL && *word!=0 ) {
  39. if (*word>='A' && *word<='Z') char_code = *word-'A';
  40. else if (*word>='a' && *word<='z') char_code = *word-'a';
  41. else return 0;// 不合法的单词
  42. //不存在此分支
  43. if( location->branch[char_code] == NULL )
  44. location->branch[char_code] = new Trie_node;    //创建空分支
  45. //转入分支
  46. location = location->branch[char_code];
  47. position++;word++;   }
  48. if (location->data != NULL) result = 0;//欲插入的单词已经存在
  49. else {    //插入数据
  50. location->data = new char[strlen(entry)+1];     //分配内存
  51. strcpy(location->data, entry);    //给data赋值表明单词存在
  52. }
  53. return result;
  54. }
  55. int main(){
  56. Trie t;
  57. char entry[100];
  58. t.insert("a", "DET");
  59. t.insert("abacus","NOUN");
  60. t.insert("abalone","NOUN");
  61. t.insert("abandon","VERB");
  62. t.insert("abandoned","ADJ");
  63. t.insert("abashed","ADJ");
  64. t.insert("abate","VERB");
  65. t.insert("this", "PRON");
  66. if (t.trie_search("this", entry))
  67. cout<<"'this' was found. pos: "<<entry<<endl;
  68. if (t.trie_search("abate", entry))
  69. cout<<"'abate' is found. pos: "<<entry<<endl;
  70. if (t.trie_search("baby", entry))
  71. cout<<"'baby' is found. pos: "<<entry<<endl;
  72. else
  73. cout<<"'baby' does not exist at all!"<<endl;
  74. }

3. 查找分析

       在trie树中查找一个关键字的时间和树中包含的结点数无关,而取决于组成关键字的字符数。而二叉查找树的查找时间和树中的结点数有关O(log2n)。
       如果要查找的关键字可以分解成字符序列且不是很长,利用trie树查找速度优于二叉查找树。如:
       若关键字长度最大是5,则利用trie树,利用5次比较可以从26^5=11881376个可能的关键字中检索出指定的关键字。而利用二叉查找树至少要进行次比较。
 

3. trie树的应用:

1. 字符串检索,词频统计,搜索引擎的热门查询

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

举例:

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

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

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

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

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

2. 字符串最长公共前缀

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

1) 给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少.  解决方案:

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

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

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

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

3.  排序

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

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

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

如后缀树,AC自动机等。

Trie树:应用于统计和排序的更多相关文章

  1. AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中?

    AVL树: 最早的平衡二叉树之一.应用相对其他数据结构比较少.windows对进程地址空间的管理用到了AVL树. 红黑树: 平衡二叉树,广泛用在C++的STL中.如map和set都是用红黑树实现的. ...

  2. 【BZOJ4212】神牛的养成计划 Trie树+可持久化Trie树

    [BZOJ4212]神牛的养成计划 Description Hzwer成功培育出神牛细胞,可最终培育出的生物体却让他大失所望...... 后来,他从某同校女神 牛处知道,原来他培育的细胞发生了基因突变 ...

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

    一. 题目 487-3279 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 274040   Accepted: 48891 ...

  4. 利用Trie树对字符串集合进行排序并计算特征值

    该算法用于将一组乱序的字符串反序列化到一个Trie树中,这个过程即可视为对字符串进行了一次排序. 还可以通过调用 GetFeatureString 将该 Trie 树重新序列化. #include & ...

  5. Trie树|字典树(字符串排序)

    有时,我们会碰到对字符串的排序,若采用一些经典的排序算法,则时间复杂度一般为O(n*lgn),但若采用Trie树,则时间复杂度仅为O(n). Trie树又名字典树,从字面意思即可理解,这种树的结构像英 ...

  6. 洛谷P3065 [USACO12DEC]第一!First!(Trie树+拓扑排序)

    P3065 [USACO12DEC]第一!First! 题目链接:https://www.luogu.org/problemnew/show/P3065 题目描述 Bessie一直在研究字符串.她发现 ...

  7. [USACO12DEC]第一!First! (Trie树,拓扑排序)

    题目链接 Solution 感觉比较巧的题啊... 考虑几点: 可以交换无数次字母表,即字母表可以为任意形态. 对于以其他字符串为前缀的字符串,我们可以直接舍去. 因为此时它所包含的前缀的字典序绝对比 ...

  8. Trie树的创建、插入、查询的实现

    原文:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28977986&id=3807947 1.什么是Trie树 Tr ...

  9. 字典树(Trie树)

    1. trie基础 (1) 是什么? Trie,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种. (2) 性质 根节点不包含字符,除根节点外每一个节点都只包含一个字符 从根节点到某一节点,路 ...

随机推荐

  1. 《网络编程》Unix 域套接字

    概述 Unix 域套接字是一种client和server在单主机上的 IPC 方法.Unix 域套接字不运行协议处理,不须要加入或删除网络报头,无需验证和,不产生顺序号,无需发送确认报文,比因特网域套 ...

  2. 记WebUtility.HtmlDecode将&nbsp;转成特殊空格的问题

    在.net中 System.Web.HttpUtility.HtmlDecode(或者WebUtility.HtmlDecode) 方法会将   解码为特殊空格(Ascii值为,对应的值为:\u00A ...

  3. using的用法

    1.using指令.using + 命名空间名字.命名空间名字可以是系统本有,也可是自己定义的class. 2.using别名.using + 别名 = 包括详细命名空间信息的具体的类型. 达成条件: ...

  4. sql--关于exec和sp_execute

    sql:exec与sp_excutesql的比较 exec与sp_execute都可以执行存储过程和批处理动态sql语句,以下所属均是关于批处理动态sql语句方面. 一.关于输入参数与输出参数 1.使 ...

  5. WPF多线程下载文件,有进度条

    //打开对话框选择文件         private void OpenDialogBox_Click(object sender, RoutedEventArgs e)         {     ...

  6. Linux Mysql Client 查询中文乱码

    1.mysql client 端设置编码为utf8 set character_set_results=utf8; 2.连接linux的客户端的编码也要设置为utf8(比如xshell,putty等)

  7. MVC-03 控制器(2)

    五.ActionResult解说 ActionResult的继承图: 类型 Controller辅助方法 用途帮助 ContentResult Content 回传一个用户自定义的文字属性 Empty ...

  8. 转 释一首美国民谣:沉默之音(The Sound Of Silence)

    Ask not what your country can do for you , ask what you can do for your country.    六十年代对美国而言是个多事之秋的 ...

  9. 转: css3动画简介以及动画库animate.css的使用

    ~~~ transition  animation 和 animate.css 在这个年代,你要是不懂一点点css3的知识,你都不好意思说你是个美工.美你妹啊,请叫我前端工程师好不好.呃..好吧,攻城 ...

  10. Strata 2014 上的 AzureCAT 粉笔会谈

     本周,AzureCAT 团队非常高兴在 Strata 会议上首次集体亮相.对于那些对 AzureCAT 团队不太熟悉的人来说,我们是 Microsoft 云与企业部门一个核心的国际性团队,由大约 ...