`
cloudtech
  • 浏览: 4604277 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

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等其他符合题目要求的变量。



代码:
// 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; //根结点(指针) 

};

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




分享到:
评论

相关推荐

    用Trie树实现词频统计和单词查询

    一个简单的C语言程序:用Trie树实现词频统计和单词查询

    Python实现Trie树

    用Python实现Trie树的应用,并可以对英汉词典进行导入和检索、添加和删除,最终可以将导入的英汉词典保存到本地磁盘。内附两个.py文件,分别是tree.py和d_gui.py,tree.py是类和方法,d_gui.py是图形界面;一个.txt...

    35丨Trie树:如何实现搜索引擎的搜索关键词提示功能?1

    为了方便快速输入,当你在搜索引擎的搜索框中,输入要搜索的文字的某一部分的时候,搜索引擎就会自动弹出下拉框,里面是各种关键词提示。当然,这样一个问题可以有多种解决

    C++/C Trie树算法

    用C实现的数据结构Trie树算法 实验的函数的trie树的插入 搜索和删除

    trie树的实现(C)

    trie.c中定义了trie树的操作函数; trie.h为相应的头文件; test.c用于测试相关的函数。 在trie.c中,关于查找定义了两个函数,一个是find(),一个是search(),二者的区别是,前者仅判断一个字符串是否在树中出现,...

    基于双数组Trie_树中文分词研究

    对双数纽Trie 树(Double-Array Trie)分词算法进行了优化:在采用... 分词系统,并与其他几种分词方法进行对比,结果表明,优化后的双数纽Trie 树插入速度和 空间利用率得到了很大提高,且分词查询效率也得到了提高.

    Trie 树实现的源码

    Trie 树实现的源码,用C++编写实现,做自然语言处理的朋友可以参考一下

    基本Trie树的实现

    Trie是一种树型数据结构,用于存储字符串,可以实现字符串的快速查找。Trie的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。...适用范围:统计和排序大量的字符串

    Trie树入门,很容易上手

    很容易上手的Trie树入门,特别适合于acm初学者

    Trie树 linux32 SDK V3.0

    2、Trie树SDK中的API支持以下功能 1)插入节点 2)精确删除节点 3)正向模糊匹配 4)反向模糊匹配 5)精确查询节点 6)获取头(尾)节点 7)删除头(尾)节点 8)排序 9)支持多级树 10)支持强大的查询节点功能 ...

    双数组Trie树算法优化及其应用研究.

    Double Array Trie是TRIE树的一种变形,它是在保证TRIE树检索速度的前提下,提高空间利用率而提出的一种数据结构,本质上是一个确定有限自动机(deterministic finite automaton,简称DFA)。 所谓的DFA就是一个能实现...

    从trie树谈到后缀树

    网上大神的总结,从trie树谈到后缀树,常用的字符串匹配算法

    双数组Trie树算法优化及其应用研究.pdf

    双数组Trie树算法优化及其应用研究.pdf 双数组Trie树算法优化及其应用研究.pdf

    Acm Trie树

    这是一个ACM算法,Trie树,他能很好的解决字符问题

    Python Trie树实现字典排序

    Trie树是一种很常用的树结构,它被广泛用于各个方面,比如字符串检索、中文分词、求字符串最长公共前缀和字典排序等等,而且在输入法中也能看到Trie树的身影。 什么是Trie树 Trie树通常又称为字典树、单词查找树或...

    Trie树 结构描述 实现方法 用法

    Trie树是一棵度 m ≥ 2 的树,它的每一层分支不是靠整个关键码的值来确定,而是由关键码的一个分量来确定

    基于Trie树模仿谷歌百度搜索框提示

    基于Trie树模仿谷歌百度搜索框提示。写的比较简单、代码漏洞之处欢迎指正。

    ACM Trie树 模板 字典树

    ACM Trie树 模板,字典树模板,数据结构

    trie树模板,acm竞赛

    trie树模板,acm竞赛,可以进行适当的修改就可以解决问题,在进行字符串处理的时候尤其能用到。

    Trie树 win32 SDK V3.0

    2、Trie树SDK中的API支持以下功能 1)插入节点 2)精确删除节点 3)正向模糊匹配 4)反向模糊匹配 5)精确查询节点 6)获取头(尾)节点 7)删除头(尾)节点 8)排序 9)支持多级树 10)支持强大的查询节点功能 ...

Global site tag (gtag.js) - Google Analytics