编程学习笔记13--字典树及其运用
2014-12-29 19:24
351 查看
字典树简介
字典树 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie把要查找的关键词看作一个字符序列,并根据构成关键词字符的先后顺序构造用于检索的树结构;一棵m度的Trie树或者为空,或者由m棵m度的Trie树构成
特别地:和二叉查找树不同,在Trie树中,每个结点上并非存储一个元素
特点
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
原理
利用串构建一个字典树,这个字典树保存了串的公共前缀信息,因此可以降低查询操作的复杂度。下面以英文单词构建的字典树为例,这棵Trie树中每个结点包括26个孩子结点,因为总共有26个英文字母(假设单词都是小写字母组成),
#define MAX 26 typedef struct TrieNode //Trie结点声明 { bool isStr; //标记该结点处是否构成单词 struct TrieNode *next[MAX]; //儿子分支 }Trie;
其中next是一个指针数组,存放着指向各个孩子结点的指针。
Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',因此根结点中数组next下标为'a'-97的值不为NULL,其他同理,构建的Trie树如图所示,红色结点表示在该处可以构成一个单词。很显然,如果要查找单词"abc"是否存在,查找长度则为O(len),len为要查找的字符串的长度。而若采用一般的逐个匹配查找,则查找长度为O(len*n),n为字符串的个数。显然基于Trie树的查找效率要高很多。但是却是以空间为代价的,比如图中每个结点所占的空间都为(26*4+1)Byte=105Byte,那么这棵Trie树所占的空间则为105*8Byte=840Byte,而普通的逐个查找所占空间只需(3+2+2+3)Byte=10Byte。
Trie树的操作
1.插入
假设存在字符串str,Trie树的根结点为root。i=0,p=root。1)取str[i],判断p->next[str[i]-97]是否为空,若为空,则建立结点temp,并将p->next[str[i]-97]指向temp,然后p指向temp;
若不为空,则p=p->next[str[i]-97];
2)i++,继续取str[i],循环1)中的操作,直到遇到结束符'\0',此时将当前结点p中的isStr置为true。
2.查找
假设要查找的字符串为str,Trie树的根结点为root,i=0,p=root1)取str[i],判断判断p->next[str[i]-97]是否为空,若为空,则返回false;若不为空,则p=p->next[str[i]-97],继续取字符。
2)重复1)中的操作直到遇到结束符'\0',若当前结点p不为空并且isStr为true,则返回true,否则返回false。
3.删除
删除可以以递归的形式进行删除。
Problem Description
Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).Input
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.
注意:本题只有一组测试数据,处理到文件结束.
Output
对于每个提问,给出以该字符串为前缀的单词的数量.
#include<stdio.h> #include<stdlib.h> #include<string.h> #define M 26 typedef struct Node { int sub_str_count;//用来记录字串的个数,当初没有想到加入这样一个变量来记录子串个数 struct Node *next[M]; }T_Node; struct Q_queue { T_Node *a[100000]; int head; int rear; }; void Init_Struct(T_Node *one) { int i=0; one->sub_str_count=1; for(i=0;i<M;i++) { one->next[i]=NULL; } } int find_2(T_Node *root,char *str) /*这种办法是先看看单词是不是在字典树里面,如果是,然后再访问最后一个节点, 作为开始,看看节点的个数*/ { struct Q_queue Q; T_Node *p,*q=root; int i=0,count=0,sum=0; int j; Q.head=0; Q.rear=0; for(j=0;str[j]!='\0';j++) { if(q->next[str[j]-'a']==NULL) { return -1; } else { q=q->next[str[j]-'a']; } } Q.a[Q.rear++]=q; while(Q.head<Q.rear) { p=Q.a[Q.head++]; count=0; for(i=0;i<M;i++) { if(p->next[i]==NULL) { count++; } else { Q.a[Q.rear++]=p->next[i]; } } if(count==M) { sum++; } } return sum; } void insert(char *str,T_Node *root) { int i=0; T_Node *p=root; T_Node *t; for(i=0;str[i]!='\0';i++) { if(p->next[str[i]-'a']==NULL)//说明移动到最后了,就是这个字母在没有在这个位置出现过 { t=(T_Node*)malloc(sizeof(T_Node)); Init_Struct(t); p->next[str[i]-'a']=t; p=p->next[str[i]-'a']; } else { p=p->next[str[i]-'a'];//沿着路线向后移动 p->sub_str_count++; } } } int visit(T_Node *root,char *str) { T_Node *p=root; int i=0; for(i=0;str[i]!='\0';i++) { if(p->next[str[i]-'a']==NULL) { return -1; } p=p->next[str[i]-'a']; } return p->sub_str_count; } int main() { T_Node root; int i; int n=6; int m=6; int count=0; char str[80]; Init_Struct(&root);//初始化root的next数组为NULL freopen("input.txt","r",stdin); while(n--) { gets(str); insert(str,&root); } while(m--) { gets(str); printf("%d\n",find_2(&root,str)); } return 0; }
相关文章推荐
- Scala中链式调用风格的实现代码实战及其在Spark编程中的广泛运用之Scala学习笔记-41
- 第51讲:Scala中链式调用风格的实现代码实战及其在Spark编程中的广泛运用学习笔记
- arm学习笔记一(arm概述及其基本编程模型)
- Linux程序设计学习笔记----网络通信编程API及其示例应用
- <<Linux内核完全剖析 --基于0.12内核>> 学习笔记 第4章 80x86保护模式及其编程 4.1 80x86系统寄存器和系统指令
- UNIX环境编程学习笔记(13)——文件I/O之标准I/O流
- arm学习笔记一(arm概述及其基本编程模型)
- Java学习笔记——String类及其常见功能(13)
- <<Linux内核完全剖析 --基于0.12内核>>学习笔记 第4章 80x86保护模式及其编程 4.8 保护模式编程初始化
- <<Linux内核完全剖析 --基于0.12内核>>学习笔记 第4章 80x86保护模式及其编程 4.3 分段机制
- Struts2学习笔记03----Struts2中的VO、ModelDriven机制及其运用
- UNIX环境编程学习笔记(13)——文件I/O之标准I/O流
- <<Linux内核完全剖析 --基于0.12内核>>学习笔记 第4章 80x86保护模式及其编程 4.6 中断和异常处理
- 内核编程学习笔记(004) 对某某游戏的驱动双开的分析及其学习
- c++ 学习笔记(高级linux编程) day13
- 编程学习笔记12--树状数组的运用
- PHP学习笔记第【13】天(2014.11.16)——oop编程
- <<Linux内核完全剖析 --基于0.12内核>>学习笔记 第4章 80x86保护模式及其编程 4.7 任务管理
- day13:JavaScript DOM编程学习笔记05
- <<Linux内核完全剖析 --基于0.12内核>>学习笔记 第4章 80x86保护模式及其编程 4.5 保护