您的位置:首页 > 其它

字典树(链式+数组模拟)--最基础的算法,最详细的注释

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:因为第一个链式结构用的是动态生成的方法,其实两个用的内存差不多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: