字典树(链式+数组模拟)--最基础的算法,最详细的注释
2017-09-02 15:07
204 查看
知识整理啦啦啦
承前启后,再挂一个入门算法的典范
字典树
啥是字典树,照我看这其实算不上一个算法,就是数据结构里树的第一种表示方式
精髓在用法上,一般用来查询前缀词数量和单词是否存在
一言以蔽之,用以高效查询上述两类问题的一种存储结构
上代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 26 //26个字母
struct tree{
struct tree *childs
; //类似突触用以下一节点
int count; //计数,有多少单词经过此节点,也就是到此节点为止的前缀词数
int final; //标志,表示单词到该字母时结束
};
struct tree *root; //入口,不存储信息,用来找到这棵树
//另,全局变量有一个好处 不占用有限的函数堆内存
void insert(char *x){ //插入单词,传入一个字符串
int length; //记录单词长度
struct tree *p,*q; //用以操作树的节点的指针
length=strlen(x); //获取单词长度
p=root; //进入树中
for(int i=0;i<length;i++){ //从头遍历单词
if(p->childs[x[i]-'a']!=null){ //也从树的头处开始询问当前字母是否存在
p=p->childs[x[i]-'a']; //存在,进入该字母所在的节点
}
else{
q=new tree; //不存在,先建立这样一个节点
p->childs[x[i]-'a']=q; //将指针指过去
p=q; //同样进入这个节点
}
p->count++;//该单词经过了这个节点,故计数加一
}
p->final=1; //遍历完了,指向的是最后一个字母,设置结束标志
}
int findprefix(char *x){ //传入前缀,查询前缀词数
int length; //长度
struct tree *p; //用以操作树的节点的指针
length=strlen(x); //获取长度
if(!length)return 0; //空串,返回0个
p=root; //进入树中
for(int i=0;i<length;i++){ //遍历前缀
if(p->childs[x[i]-'a']!=null){ //也从树的头处开始询问当前字母是否存在
p=p->childs[x[i]-'a']; //存在,进入该字母所在的节点
}else{
return 0; //不存在,就没这个前缀,返回0个
}
return p->count; //遍历完了,有count个单词经过这个节点就说明有这么多个前缀词
}
int findword(char *x){ //传入单词,查询前缀词数
int length; //长度
struct tree *p; //用以操作树的节点的指针
length=strlen(x); //获取长度
if(!length)return 0; //空串,返回0表示不存在
p=root; //进入树中
for(int i=0;i<length;i++){ //遍历单词
if(p->childs[x[i]-'a']!=null){ //也从树的头处开始询问当前字母是否存在
p=p->childs[x[i]-'a']; //存在,进入该字母所在的节点
}else{
return 0; //不存在,就没这个单词,返回0表示不存在
}
if(p->final==1) return 1: //遍历完了,若最后一个字母是某词的结尾,则表示有该词,返回存在
else return 0; //否则,表示这个单词是某个前缀,返回不存在
}
int main(){
root= new tree;
/*
输入单词或查询
*/
return 0;
}
get到了这个算法,但
回头一想查,询单词是一件很简单的事对吧
那为啥要有这个算法呢
因为快,有多快呢,举个栗子
你的词库里有10亿个单词(嗯 尽是些长单词)
如果是用朴素方法,需要多少开销呢
查询单词还好些,用二分也没几次,但你要先排序(时间复杂度天文数字)
要是查询前缀,一个个匹配(大天文数字)
但对字典树来说,只要遍历一个单词就好了,和词库容量无关,多少词都这个速度,看起来好像挺快的
已经这么快了,只能换个角度去优化了,在内存上有好多悬空指针啊,浪费!
于是
数组模拟字典树(降速减容)
#include<stdio.h>
#include<string.h>
#define N 900000
int
4000
tree
[26],count
; //看成N个连续的地址空间 每个空间有26个通道和一个计数
int pc; //模拟连续内存的首地址,是几无所谓啦,默认0
void insert(char x*){ //插入单词
int length;
length=strlen(x); //获取单词长度
int p=0; //类似指针,数值就是地址了
for(int i=0;i<length;i++){ //遍历单词
int j=x[i]-'a'; //字母转义对应26个下标
if(tree[p][j]==0) //0地址的空间作为root了,判断该字母是否存在
tree[p][j]=++pc; //不存在,选取下一个空的空间连通,作为该字母的节点
//说明下参数 p表示当前节点的地址,j表示字母的通道,数值就是通道那头节点的地址了
p=tree[p][j]; //进入该字母的节点
count[p]++; //单词经过了这个节点,计数加一
}
}
int find(char *x){ // 传入前缀
int length;
length=strlen(x); //获取长度
p=0; //类似指针,数值就是地址了
for(int i=0;i<length;i++){ //遍历前缀
int j=x[i]-'a'; //字母转义对应26个下标
if(tree[p][j]==0) return 0; //不存在,结束没这个前缀
p=tree[p][j]; //进入该字母节点
}
return count[p]; //遍历结束,返回前缀词数
}
ps:因为第一个链式结构用的是动态生成的方法,其实两个用的内存差不多
承前启后,再挂一个入门算法的典范
字典树
啥是字典树,照我看这其实算不上一个算法,就是数据结构里树的第一种表示方式
精髓在用法上,一般用来查询前缀词数量和单词是否存在
一言以蔽之,用以高效查询上述两类问题的一种存储结构
上代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 26 //26个字母
struct tree{
struct tree *childs
; //类似突触用以下一节点
int count; //计数,有多少单词经过此节点,也就是到此节点为止的前缀词数
int final; //标志,表示单词到该字母时结束
};
struct tree *root; //入口,不存储信息,用来找到这棵树
//另,全局变量有一个好处 不占用有限的函数堆内存
void insert(char *x){ //插入单词,传入一个字符串
int length; //记录单词长度
struct tree *p,*q; //用以操作树的节点的指针
length=strlen(x); //获取单词长度
p=root; //进入树中
for(int i=0;i<length;i++){ //从头遍历单词
if(p->childs[x[i]-'a']!=null){ //也从树的头处开始询问当前字母是否存在
p=p->childs[x[i]-'a']; //存在,进入该字母所在的节点
}
else{
q=new tree; //不存在,先建立这样一个节点
p->childs[x[i]-'a']=q; //将指针指过去
p=q; //同样进入这个节点
}
p->count++;//该单词经过了这个节点,故计数加一
}
p->final=1; //遍历完了,指向的是最后一个字母,设置结束标志
}
int findprefix(char *x){ //传入前缀,查询前缀词数
int length; //长度
struct tree *p; //用以操作树的节点的指针
length=strlen(x); //获取长度
if(!length)return 0; //空串,返回0个
p=root; //进入树中
for(int i=0;i<length;i++){ //遍历前缀
if(p->childs[x[i]-'a']!=null){ //也从树的头处开始询问当前字母是否存在
p=p->childs[x[i]-'a']; //存在,进入该字母所在的节点
}else{
return 0; //不存在,就没这个前缀,返回0个
}
return p->count; //遍历完了,有count个单词经过这个节点就说明有这么多个前缀词
}
int findword(char *x){ //传入单词,查询前缀词数
int length; //长度
struct tree *p; //用以操作树的节点的指针
length=strlen(x); //获取长度
if(!length)return 0; //空串,返回0表示不存在
p=root; //进入树中
for(int i=0;i<length;i++){ //遍历单词
if(p->childs[x[i]-'a']!=null){ //也从树的头处开始询问当前字母是否存在
p=p->childs[x[i]-'a']; //存在,进入该字母所在的节点
}else{
return 0; //不存在,就没这个单词,返回0表示不存在
}
if(p->final==1) return 1: //遍历完了,若最后一个字母是某词的结尾,则表示有该词,返回存在
else return 0; //否则,表示这个单词是某个前缀,返回不存在
}
int main(){
root= new tree;
/*
输入单词或查询
*/
return 0;
}
get到了这个算法,但
回头一想查,询单词是一件很简单的事对吧
那为啥要有这个算法呢
因为快,有多快呢,举个栗子
你的词库里有10亿个单词(嗯 尽是些长单词)
如果是用朴素方法,需要多少开销呢
查询单词还好些,用二分也没几次,但你要先排序(时间复杂度天文数字)
要是查询前缀,一个个匹配(大天文数字)
但对字典树来说,只要遍历一个单词就好了,和词库容量无关,多少词都这个速度,看起来好像挺快的
已经这么快了,只能换个角度去优化了,在内存上有好多悬空指针啊,浪费!
于是
数组模拟字典树(降速减容)
#include<stdio.h>
#include<string.h>
#define N 900000
int
4000
tree
[26],count
; //看成N个连续的地址空间 每个空间有26个通道和一个计数
int pc; //模拟连续内存的首地址,是几无所谓啦,默认0
void insert(char x*){ //插入单词
int length;
length=strlen(x); //获取单词长度
int p=0; //类似指针,数值就是地址了
for(int i=0;i<length;i++){ //遍历单词
int j=x[i]-'a'; //字母转义对应26个下标
if(tree[p][j]==0) //0地址的空间作为root了,判断该字母是否存在
tree[p][j]=++pc; //不存在,选取下一个空的空间连通,作为该字母的节点
//说明下参数 p表示当前节点的地址,j表示字母的通道,数值就是通道那头节点的地址了
p=tree[p][j]; //进入该字母的节点
count[p]++; //单词经过了这个节点,计数加一
}
}
int find(char *x){ // 传入前缀
int length;
length=strlen(x); //获取长度
p=0; //类似指针,数值就是地址了
for(int i=0;i<length;i++){ //遍历前缀
int j=x[i]-'a'; //字母转义对应26个下标
if(tree[p][j]==0) return 0; //不存在,结束没这个前缀
p=tree[p][j]; //进入该字母节点
}
return count[p]; //遍历结束,返回前缀词数
}
ps:因为第一个链式结构用的是动态生成的方法,其实两个用的内存差不多
相关文章推荐
- UVA-1368-DNA Consensus String 基础题 贪心 模拟 详细注释
- 后缀数组——罗穗骞倍增算法详细注释
- 快速幂+叉乘计算--最基础的算法,最详细的注释
- 矩阵快速幂--最基础的算法,最详细的注释
- 后缀数组——罗穗骞倍增算法详细注释
- JavaScript数据结构与算法(五) 数组基础算法
- 算法基础(八):超详细最优二叉树构建(1)
- 【Java基础】--算法与数组
- 【算法】数组模拟链表
- LeetCode Merge Two Sorted Lists && 基础算法-合并有序数组
- 黑马程序员 JAVA基础 数组、数组工具类、文档注释总结
- 基础算法(3)、搜索与模拟
- JAVA学习笔记:基础算法(附Java与C之间检查数组越界的差异)
- C++15道基础算法题---(1)合并已排序数组
- 迪杰斯特拉/dijkstra 算法模板(详细注释)
- Java学习之数组1(1.数组的声明;2.元素为引用数据类型的数组;3.关于main方法里的String[] args;4.数组排序;5.数3退1 数组算法,(用数组模拟链表);6数组查找之二分法;7数组的拷贝)
- mp基础 算法 (忽略注释)
- 【技术收藏】enpaodelvzi编写:一个Java写的俄罗斯方块源码 算法简单(300行) 注释详细
- 20140723 【字典树 - 前缀树,数组模拟指针】 HDOJ 1305 Immediate Decodability
- 【算法系列学习】DP和滚动数组 [kuangbin带你飞]专题十二 基础DP1 A - Max Sum Plus Plus