(一)Trie的简单介绍

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

他的核心思想是空间换时间,空间消耗大可是插入和查询有着非常优秀的时间复杂度。

(二)Trie的定义

Trie树的键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的全部子孙都有同样的前缀(prefix),从根节点到当前结点的路径上的全部字母组成当前位置的字符串。结点能够保存当前字符串、出现次数、指针数组(指向子树)以及是否是结尾标志等等。

typedef struct Trie_Node
{
char count[15]; //单词前缀出现的次数
struct Trie_Node* next[MAXN]; //指向各个子树的指针
bool exist; //标记结点处是否构成单词
}Trie;

Trie树能够利用字符串的公共前缀来节约存储空间,例如以下图所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

它有3个基本性质:

(1) 根节点不包括字符,除根节点外每个节点都仅仅包括一个字符。

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

(3) 每一个节点的全部子节点包括的字符都不同样。

(三)Trie树的基本操作

(1)插入操作

按下标索引逐个插入字母,若当前字母存在则继续下一个,否则new出当前字母的结点,所以插入的时间复杂度仅仅和字符串的长度n有关,为O(n)。

void Insert(Trie *root, char* s,char *add)
{
Trie *p=root;
while(*s!='\0')
{
if(p->next[*s-'a']==NULL)
{
p->next[*s-'a']=createNode();
}
p=p->next[*s-'a'];
// p->count=add;
++s;
}
p->exist=true;
strcpy(p->count,add);
}

(2)查询操作

和插入操作相仿,若查询途中某一个结点并不存在,则直接就return返回。否则继续下去,当字符串结束时,trie树上也有结束标志。那么证明此字符串存在,return true;

int Search(Trie* root,const char* s)
{
Trie *p=root;
while(*s!='\0')
{
p=p->next[*s-'a'];
if(p==NULL)
return 0;
++s;
}
return p->count;
}

(3)删除操作

一般来说,对Trie单个结点的删除操作不常见,所以我在这里也仅仅提供递归删除整个树的操作

void del(Trie *root)
{
for(int i=0;i<MAXN;i++)
{
if(root->next[i]!=NULL)
{
del(root->next[i]);
}
}
// free(root);
delete root;
}

(4)遍历操作

假设我们想要将trie中的字符串排序输出,直接先序遍历就可以。

void Print(Trie *root)
{
Trie *p=root;
if(p->exist)
cout<<p->name<<": "<<p->count<<endl;
for(int i=0;i<26;i++)
{
if(p->next[i]!=NULL){
Print(p->next[i]);
}
}
}

(四)Trie树的详细应用

(1)统计前缀出现的次数

这是Trie最主要的应用,每一个结点的字母使用count记录出现的次数就可以。

这里提供一道题目,hdu 1251供大家练习。

//hdu 1251   统计前缀出现次数
#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
const int MAXN=26;
typedef struct Trie_Node
{
int count; //单词前缀出现的次数
struct Trie_Node* next[MAXN]; //指向各个子树的指针
bool exist; //标记结点处是否构成单词
}Trie;
Trie* createNode()
{
//Trie* p =(Trie*)malloc(sizeof(Trie));
Trie *p=new Trie;
p->count=0;
p->exist=false;
memset(p->next,0,sizeof(p->next));
return p;
}
void Insert(Trie *root, const char* s)
{
Trie *p=root;
while(*s!='\0')
{
if(p->next[*s-'a']==NULL)
{
p->next[*s-'a']=createNode();
}
p=p->next[*s-'a'];
p->count+=1;
++s;
}
p->exist=true;
} int Search(Trie* root,const char* s)
{
Trie *p=root;
while(*s!='\0')
{
p=p->next[*s-'a'];
if(p==NULL)
return 0;
++s;
}
return p->count;
} void del(Trie *root)
{
for(int i=0;i<MAXN;i++)
{
if(root->next[i]!=NULL)
{
del(root->next[i]);
}
}
// free(root);
delete root;
}
int main()
{
char s[15];
bool flag=false;
Trie* root=createNode();
while(gets(s))
{
if(flag)
{
int ans=Search(root,s);
printf("%d\n",ans);
}
else
{
if(strlen(s)!=0)
Insert(root,s);
}
if(strlen(s)==0)
flag=true;
}
del(root);
return 0;
}

(2)翻译(password,明文)

给定一组字符串s。k我们输入k则须要翻译成s。也就是说两者是映射关系。接下来我们给出一段话,让你翻译出正常的文章。

用map固然简便。可是Trie的效率更加高。

仅仅须要在k的结尾结点出记录下s就可以。

这里也提供一道题目。hdu 1075。(被凝视的是我原来的程序,wa了,有大神看出来麻烦告诉我一下。谢谢)。

/*
//hdu 1075映射
#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
using namespace std;
const int MAXN=26;
typedef struct Trie_Node
{
char count[15]; //单词前缀出现的次数
struct Trie_Node* next[MAXN]; //指向各个子树的指针
bool exist; //标记结点处是否构成单词
}Trie;
Trie* createNode()
{
Trie* p =(Trie*)malloc(sizeof(Trie));
p->exist=false;
memset(p->next,0,sizeof(p->next));
return p;
}
void Insert(Trie *root, char* s,char *add)
{
Trie *p=root;
while(*s!='\0')
{
if(p->next[*s-'a']==NULL)
{
p->next[*s-'a']=createNode();
}
p=p->next[*s-'a'];
// p->count=add;
++s;
}
p->exist=true;
strcpy(p->count,add);
} void Search(Trie* root, const char* s)
{
Trie *p=root;
while(*s!='\0')
{
if(p->next[*s-'a']==NULL)
{
printf("%s",s);
return ;
} p=p->next[*s-'a'];
++s;
}
if(p->exist)
printf("%s",p->count);
else
printf("%s",s);
} void del(Trie *root)
{
for(int i=0;i<MAXN;i++)
{
if(root->next[i]!=NULL)
{
del(root->next[i]);
}
}
free(root);
}
int main()
{
char text[3013],from[15],to[15];
Trie* root=createNode();
scanf("%s",from);
while(scanf("%s",from),strcmp(from,"END"))
{
scanf("%s",to);
Insert(root,to,from);
}
scanf("%s",from);
getchar();
while(gets(text),strcmp(text,"END"))
{
int len=strlen(text);
for(int i=0;i<len;i++)
{
if(islower(text[i]))
{
int j=0;
char temp[15];
memset(temp,'\0',sizeof(temp));
while(islower(text[i]))
temp[j++]=text[i++];
Search(root,temp); }
if(!islower(text[i]))
printf("%c",text[i]);
}
printf("\n");
}
return 0;
}
*/ #include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<string> using namespace std; struct node{
char dic[15];
node * next[26];
bool flag;
}*root; node *build()
{
node *p=(node *)malloc(sizeof(node));
for(int i=0;i<26;i++)
p->next[i]=NULL;
p->flag=false;
return p;
} void insert(char *earth,char *mars)
{
int len=strlen(mars);
node *p;
p=root;
for(int i=0;i<len;i++)
{
if(p->next[mars[i]-'a']==NULL)
p->next[mars[i]-'a']=build();
p=p->next[mars[i]-'a'];
}
p->flag=true;
strcpy(p->dic,earth);
} void query(char *earth)
{
int len=strlen(earth);
node *p;
p=root;
for(int i=0;i<len;i++)
{
if(p->next[earth[i]-'a']==NULL)
{
printf("%s",earth);
return;
}
p=p->next[earth[i]-'a'];
}
if(p->flag)
printf("%s",p->dic);
else
printf("%s", earth);
} int main()
{
char earth[15],mars[15],ask[3010]; scanf("%s",earth);
root=build();
while(scanf("%s",earth),strcmp(earth,"END"))
{
scanf("%s",mars);
insert(earth,mars);
} scanf("%s",earth);
getchar();
while(gets(ask),strcmp(ask,"END"))
{
int len=strlen(ask);
for(int i=0;i<len;i++)
{
if(islower(ask[i]))
{
int j=0;
memset(earth,'\0',sizeof(earth));
while(islower(ask[i]))
earth[j++]=ask[i++];
query(earth);
}
if(!islower(ask[i]))
printf("%c",ask[i]);
}
printf("\n");
} return 0;
}

(3)实现搜索引擎的热门搜索排名

我的初步想法是和(1)类似。对(1)中的trie进行先序遍历,将字符串和出现次数存进一个结构体,最后对这个数组进行高速排序。时间复杂度为O(nlogn),看网上说能够利用分治+trie

+最小堆。我还没有细致搞清楚,以后研究完在加入。

(4)输入自己主动补全

事实上原理都差点儿相同。把字符串结尾处的结点当作root。进行先序遍历,就可以得出全部以输入的字符串为前缀的答案。

/ 自己主动补全
#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
const int MAXN=26;
typedef struct Trie_Node
{
int count; //单词出现的次数
struct Trie_Node* next[MAXN]; //指向各个子树的指针
bool exist; //标记结点处是否构成单词
char name[15];
}Trie;
Trie* createNode()
{
Trie* p =(Trie*)malloc(sizeof(Trie));
p->count=0;
p->exist=false;
memset(p->next,0,sizeof(p->next));
return p;
}
void Insert(Trie *root,char* word)
{
Trie *p=root;
char *s=word;
while(*s!='\0')
{
if(p->next[*s-'a']==NULL)
{
p->next[*s-'a']=createNode();
}
p=p->next[*s-'a'];
++s;
}
p->exist=true;
p->count+=1;
strcpy(p->name,word);
} Trie* Search(Trie* root, char* s)
{
Trie *p=root;
while(*s!='\0')
{
p=p->next[*s-'a'];
if(p==NULL)
return 0;
++s;
}
return p;
} void del(Trie *root)
{
for(int i=0;i<MAXN;i++)
{
if(root->next[i]!=NULL)
{
del(root->next[i]);
}
}
free(root); }
void Print(Trie *root)
{
Trie *p=root;
if(p->exist)
cout<<p->name<<": "<<p->count<<endl;
for(int i=0;i<26;i++)
{
if(p->next[i]!=NULL){
Print(p->next[i]);
}
}
}
int main()
{
char s[15];
Trie* root=createNode();
for(int i=0;i<5;i++)
{
cin>>s;
Insert(root,s);
}
while(cin>>s)
{
Trie *ans=Search(root,s);
if(ans)
Print(ans);
}
del(root);
return 0;
}

Trie树的常见应用大总结(面试+附代码实现)的更多相关文章

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

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

  2. 剑指Offer——Trie树(字典树)

    剑指Offer--Trie树(字典树) Trie树 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种的单词.对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位 ...

  3. poj_3630 trie树

    题目大意 给定一系列电话号码,查看他们之间是否有i,j满足,号码i是号码j的前缀子串. 题目分析 典型的trie树结构.直接使用trie树即可.但是需要注意,若使用指针形式的trie树,则在大数据量下 ...

  4. 双数组Trie树 (Double-array Trie) 及其应用

    双数组Trie树(Double-array Trie, DAT)是由三个日本人提出的一种Trie树的高效实现 [1],兼顾了查询效率与空间存储.Ansj便是用DAT(虽然作者宣称是三数组Trie树,但 ...

  5. Trie树之C-实现

    title: Trie树之C++实现 comments: true date: 2016-10-02 16:59:54 categories: 算法 tags: Trie树 前言 之前写了一篇偏向于理 ...

  6. Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结

    Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 ...

  7. 大数据处理-Trie树

    大数据处理--Trie树 1.1.什么是Trie树 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被 ...

  8. 数据结构 | 30行代码,手把手带你实现Trie树

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法和数据结构专题的第28篇文章,我们一起来聊聊一个经典的字符串处理数据结构--Trie. 在之前的4篇文章当中我们介绍了关于博弈论的 ...

  9. [转]双数组TRIE树原理

    原文名称: An Efficient Digital Search Algorithm by Using a Double-Array Structure 作者: JUN-ICHI AOE 译文: 使 ...

随机推荐

  1. IPv6第二层寻址,IPv6接口要求

    1. IPv6第二层寻址 IPV6地址以两种方式与第2层地址相关.第一种方式是IPV6独有的,提供了从第2层地址构建接口ID的机制.第二种方式对IPv4和IPV6都是一样的,提供了将一个IP组播地址映 ...

  2. BZOJ 4668 冷战(按秩合并并查集+LCA)

    4668: 冷战 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 627  Solved: 303[Submit][Status][Discuss] D ...

  3. Linux Shell脚本编程-信号捕获

    bash编程的信号捕获:  kill -l 显示当前系统可用信号(trap -l)  获取帮助:man 7 single 常用信号: 1) SIGHUP  无须重启进程而让其重读配置文件  2) SI ...

  4. 华为P30系列新增“无线投屏”功能

    3月26日法国巴黎全球首发之后,4月11日华为又移师上海举办2019春季新品发布盛典,新一代拍照旗舰P30.P30 Pro正式登陆国内. 除了感光徕卡四摄带来的“彩色夜视仪“+“望远镜”的震撼拍照效果 ...

  5. vue父子组件通信传值

    父组件 -> 子组件 通过props来进行通信 父组件代码: <Children :dataName = "dataContent" /> //dataName: ...

  6. caioj 1153 扩展欧几里德算法(解不定方程)

    模板题 注意exgcd函数要稍微记一下 #include<cstdio> #include<cctype> #include<algorithm> #define ...

  7. 紫书 例题 10-4 UVa 10791(唯一分解定理)

    首先分解,然后可以发现同一个因子ai不能存在于两个以上的数中 因为求的是最小公倍数,如果有的话就可以约掉 所以数字必然由ai的pi次方的乘积组成,那么显然,在 a最小为2,而b大于2的情况下a*b&g ...

  8. Atitit.运行cmd 命令行 php

    Atitit.运行cmd 命令行 php 1. 运行cmd 命令行,调用系统命令的基础 1 1.1. 实际运行模式 1 1.2. 空格的问题 1 1.3. 中文路径的问题.程序文件读取编码设置 1 1 ...

  9. Metasploit的三种启动方式

    不多说,直接上干货! 注意:博主我用的是Kali linux 2016.2(Rolling).  msfcli 的启动 root@kali:~# msfcli -h bash: msfcli: 未找到 ...

  10. Lua,github,nginx

    个人觉得这几种工具都是大学里,至少是前几年未曾出现于课本中或者图书馆中的.而如今确实某些公司赫然出现在招聘需求中的东西. 什么是Lua,为什么要用Lua——做手机游戏而被推广的.Lua原来早在93年就 ...