数据结构-4-Trie树:应用于统计、排序与搜索 原理详解

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.

6. #pragma once

7.

8. #include <stdio.h>

9. #include "stdlib.h"

10. #include <iostream>

11. #include <string.h>

12. using namespace std;

13.

14. //宏定义

15. #define TRUE   1

16. #define FALSE   0

17. #define NULL 0

18. #define OK    1

19. #define ERROR   0

20. #define INFEASIBLE -1

21. #define OVERFLOW -2

22.

23. const int num_chars = 26;

24. class Trie {

25. public:

26.     Trie();

27.     Trie(Trie& tr);

28.     virtual ~Trie();

29.     int trie_search(const char* word, char* entry ) const;

30.     int insert(const char* word, const char* entry);

31.     int remove(const char* word, char* entry);

32. protected:

33.      struct Trie_node{

34.            char* data; //若不为空,表示从root到此结点构成一个单词

35.            Trie_node* branch[num_chars]; //分支

36.            Trie_node(); //构造函数

37.      };

38.

39.      Trie_node* root; //根结点(指针)

40.

41. };

// stdafx.h : include file for standard system include files,

// or project specific include files that are used frequently, but

// are changed infrequently

//

#pragma once

#include <stdio.h>

#include "stdlib.h"

#include <iostream>

#include <string.h>

using namespace std;

//宏定义

#define TRUE   1

#define FALSE   0

#define NULL 0

#define OK    1

#define ERROR   0

#define INFEASIBLE -1

#define OVERFLOW -2

const int num_chars = 26;

class Trie {

public:

Trie();

Trie(Trie& tr);

virtual ~Trie();

int trie_search(const char* word, char* entry ) const;

int insert(const char* word, const char* entry);

int remove(const char* word, char* entry);

protected:

struct Trie_node{

char* data; //若不为空,表示从root到此结点构成一个单词

Trie_node* branch[num_chars]; //分支

Trie_node(); //构造函数

};

Trie_node* root; //根结点(指针)

};

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.

15.     Trie_node *location = root;  //从根结点开始

16.     while( location!=NULL && *word!=0 ) {

17.         if (*word >= ‘A‘ && *word <= ‘Z‘)

18.             char_code = *word-‘A‘;

19.         else if (*word>=‘a‘ && *word<=‘z‘)

20.             char_code = *word-‘a‘;

21.         else return 0;// 不合法的单词

22.         //转入相应分支指针

23.         location = location->branch[char_code];

24.         position++;

25.         word++;

26.     }

27.     //找到,获取数据,成功返回

28.     if ( location != NULL && location->data != NULL ) {

29.         strcpy(entry,location->data);

30.         return 1;

31.     }

32.     else  return 0;// 不合法的单词

33. }

34. int Trie::insert(const char* word, const char* entry) {

35.     int result = 1, position = 0;

36.     if ( root == NULL ) root = new Trie_node;   //初始插入,根结点为空

37.     char char_code;

38.     Trie_node *location = root;   //从根结点开始

39.     while( location!=NULL && *word!=0 ) {

40.         if (*word>=‘A‘ && *word<=‘Z‘) char_code = *word-‘A‘;

41.         else if (*word>=‘a‘ && *word<=‘z‘) char_code = *word-‘a‘;

42.         else return 0;// 不合法的单词

43.

44.         //不存在此分支

45.         if( location->branch[char_code] == NULL )

46.             location->branch[char_code] = new Trie_node;    //创建空分支

47.

48.         //转入分支

49.         location = location->branch[char_code];

50.         position++;word++;   }

51.     if (location->data != NULL) result = 0;//欲插入的单词已经存在

52.     else {    //插入数据

53.         location->data = new char[strlen(entry)+1];     //分配内存

54.         strcpy(location->data, entry);    //给data赋值表明单词存在

55.     }

56.     return result;

57. }

58. int main(){

59.     Trie t;

60.     char entry[100];

61.     t.insert("a", "DET");

62.     t.insert("abacus","NOUN");

63.     t.insert("abalone","NOUN");

64.     t.insert("abandon","VERB");

65.     t.insert("abandoned","ADJ");

66.     t.insert("abashed","ADJ");

67.     t.insert("abate","VERB");

68.     t.insert("this", "PRON");

69.     if (t.trie_search("this", entry))

70.         cout<<"‘this‘ was found. pos: "<<entry<<endl;

71.     if (t.trie_search("abate", entry))

72.         cout<<"‘abate‘ is found. pos: "<<entry<<endl;

73.     if (t.trie_search("baby", entry))

74.         cout<<"‘baby‘ is found. pos: "<<entry<<endl;

75.     else

76.         cout<<"‘baby‘ does not exist at all!"<<endl;

77. }

// Test.cpp : Defines the entry point for the console application.

//

#include "stdafx.h"

Trie::Trie_node::Trie_node() {

data = NULL;

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

branch[i] = NULL;

}

Trie::Trie():root(NULL) {}

Trie::~Trie(){}

int Trie::trie_search(const char* word, char* entry ) const {

int position = 0;  //层数

char char_code;

Trie_node *location = root;  //从根结点开始

while( location!=NULL && *word!=0 ) {

if (*word >= ‘A‘ && *word <= ‘Z‘)

char_code = *word-‘A‘;

else if (*word>=‘a‘ && *word<=‘z‘)

char_code = *word-‘a‘;

else return 0;// 不合法的单词

//转入相应分支指针

location = location->branch[char_code];

position++;

word++;

}

//找到,获取数据,成功返回

if ( location != NULL && location->data != NULL ) {

strcpy(entry,location->data);

return 1;

}

else  return 0;// 不合法的单词

}

int Trie::insert(const char* word, const char* entry) {

int result = 1, position = 0;

if ( root == NULL ) root = new Trie_node;   //初始插入,根结点为空

char char_code;

Trie_node *location = root;   //从根结点开始

while( location!=NULL && *word!=0 ) {

if (*word>=‘A‘ && *word<=‘Z‘) char_code = *word-‘A‘;

else if (*word>=‘a‘ && *word<=‘z‘) char_code = *word-‘a‘;

else return 0;// 不合法的单词

//不存在此分支

if( location->branch[char_code] == NULL )

location->branch[char_code] = new Trie_node;    //创建空分支

//转入分支

location = location->branch[char_code];

position++;word++;   }

if (location->data != NULL) result = 0;//欲插入的单词已经存在

else {    //插入数据

location->data = new char[strlen(entry)+1];     //分配内存

strcpy(location->data, entry);    //给data赋值表明单词存在

}

return result;

}

int main(){

Trie t;

char entry[100];

t.insert("a", "DET");

t.insert("abacus","NOUN");

t.insert("abalone","NOUN");

t.insert("abandon","VERB");

t.insert("abandoned","ADJ");

t.insert("abashed","ADJ");

t.insert("abate","VERB");

t.insert("this", "PRON");

if (t.trie_search("this", entry))

cout<<"‘this‘ was found. pos: "<<entry<<endl;

if (t.trie_search("abate", entry))

cout<<"‘abate‘ is found. pos: "<<entry<<endl;

if (t.trie_search("baby", entry))

cout<<"‘baby‘ is found. pos: "<<entry<<endl;

else

cout<<"‘baby‘ does not exist at all!"<<endl;

}

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自动机等。

时间: 2025-01-05 17:01:35

数据结构-4-Trie树:应用于统计、排序与搜索 原理详解的相关文章

数据结构-6-深度广度遍历搜索原理详解

深度广度遍历搜索的定义想必大家都能熟练的掌握了,下面我就通过一个图的实例,把应用的代码直接贴上供大家参考,以后可以直接借鉴或者使用. #include <iostream> #include <string> #include "Queue.h" using namespace std; //图的邻接矩阵存储表示 #define INFINITY INT_MAX #define MAX_VERTEX_NUM 20 typedef enum {DG, DN, UD

【数据结构】Trie树

1.Trie树简介 Trie树,又称字典树.前缀树,被用于信息检索(information retrieval)的数据结构.Trie一词便来自于单词retrieval.基本思想:用字符串的公共前缀降低查询时间.比如,在最优的查询二叉树中查询关键字的时间复杂度为M * log N,M是字符串最大长度,N为字符串数量:而用Trie树时,只需O(M)时间. [1] 中给出一个简单Trie树例子,蓝色表示一个单词结尾:该Trie树存储的单词为the, their, there, a, any, answ

【数据结构】Trie树的应用:查询IP地址的ISP(Java实现)

查询IP地址的ISP 给定一个IP地址,如何查询其所属的ISP,如:中国移动(ChinaMobile),中国电信(ChinaTelecom),中国铁通(ChinaTietong)? 现在网上有ISP的IP地址区段可供下载,比如中国移动的IP地址段 103.20.112.0/22 103.21.176.0/22 111.0.0.0/10 112.0.0.0/10 117.128.0.0/10 120.192.0.0/10 183.192.0.0/10 211.103.0.0/17 211.136.

数据结构 - 简单选择排序(simple selection sort) 详解 及 代码(C++)

数据结构 - 简单选择排序(simple selection sort) 本文地址: http://blog.csdn.net/caroline_wendy/article/details/28601965 选择排序(selection sort) : 每一趟在n-i+1个记录中选取关键字最小的记录作为有序序列中第i个记录. 简单选择排序(simple selection sort) : 通过n-i次关键字之间的比较, 从n-i+1个记录中选出关键字最小的记录, 并和第i个记录交换. 选择排序需

数据结构之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的公共前缀是&qu

trie树模板(统计难题)

统计难题 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131070/65535 K (Java/Others)Total Submission(s): 36675    Accepted Submission(s): 13637 Problem Description Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的

数据结构 - 树形选择排序 (tree selection sort) 详解 及 代码

http://blog.csdn.net/yj_1989/article/details/46598579http://blog.csdn.net/yj_1989/article/details/46598581http://blog.csdn.net/yj_1989/article/details/46598605http://blog.csdn.net/yj_1989/article/details/46598607http://blog.csdn.net/yj_1989/article/d

Java集合排序及java集合类详解--(Collection, List, Set, Map)

1         集合框架 1.1         集合框架概述 1.1.1         容器简介 到目前为止,我们已经学习了如何创建多个不同的对象,定义了这些对象以后,我们就可以利用它们来做一些有意义的事情. 举例来说,假设要存储许多雇员,不同的雇员的区别仅在于雇员的身份证号.我们可以通过身份证号来顺序存储每个雇员,但是在内存中实现呢?是不是要准备足够的内存来存储1000个雇员,然后再将这些雇员逐一插入?如果已经插入了500条记录,这时需要插入一个身份证号较低的新雇员,该怎么办呢?是在内

分区函数Partition By的与row_number()的用法以及与排序rank()的用法详解(获取分组(分区)中前几条记录)(转)

转载地址:http://www.cnblogs.com/linJie1930906722/p/6036053.html partition by关键字是分析性函数的一部分,它和聚合函数不同的地方在于它能返回一个分组中的多条记录,而聚合函数一般只有一条反映统计值的记录,partition by用于给结果集分组,如果没有指定那么它把整个结果集作为一个分组,分区函数一般与排名函数一起使用. 准备测试数据: create table Student --学生成绩表 ( id int, --主键 Grad