Trie树简单讲解

字典树(Trie Tree)

trie树就是用来对于大量的文本统计,储存与查找的一种数据结构,下面简单介绍两种trie树的实现方式。


  • 多叉trie树

对于一个字符集大小为S的文本,我们可以建立一棵S叉树,将文本统计。

具体实现如下:

首先我们对于一个节点,开一个大小为S的数组,来表示它对于的子节点。比如对于小写字母的字符集,大小为26,我们就可以定义如下的节点:(我们将a~z重新编号为0~25)

struct node{
    int son[26];
    int flag;
};

其中son[i],表示当前这个节点下一个字母编号为i所对应的节点的编号,我们是将字母放在边上的。

所以对于一个字符串,我们可以用O(len)的时间插入到trie树中,len为字符串长度。

int tot;//表示节点个数
node trie[N];/trie树节点
int getid(char c){return c-'a';}
//获取字符编号
void insert(char *s,int len){
    int nowid=0;//当前的节点编号,这里我们把0号点当做根节点。
    for(int i=0,j;i<len;i++){
        j=getid(s[i]);//获取当前的字符编号
        if(!trie[nowid].son[j])
            trie[nowid].son[j]=++tot;//如果没有该结点,那么就将新建一个节点,节点个数增加。
        nowid=trie[nowid].son[j];
        //走到下一个节点。
    }
    trie[nowid].flag++;
    //将结束位置做上标记,表示有一个字符串在这里结束。
}

而我们发现,当字符集很大时,空间复杂度最坏是len×S的,所以有没有更好的方法呢?下面就来介绍第二种。

  • 链表式trie树(时间换空间法)

我们可以像建图一样,将trie树建出,而没出现的节点就可以不用建,那么将大大节省空间复杂度,但是也有坏处就是我们不能O(1)的查找某一个节点对应的下一个某个字母的节点编号,而是需要去遍历所以的子节点,但是有时候时间换空间是明智的。

具体实现是类似的,但是在这里我们就不定义节点,可以定义一个边,如下:

struct side{
    int to,next;
    char c;//记录每条边对应的字符
};
int head[M],cnt;
//to表示下一个节点的编号和next表示链表的指针。
//head表示链表的表头,cnt记录边数

那么增加一条边就简单了:

side g[N];
void add(int a,int b,char c){
    g[++cnt]=(side){b,head[a],c};head[a]=cnt;
}

然后插入一个字符串的方式同第一种,只是细节变化了一点。

int flag[N];//记录节点标记
int tot;//表示节点个数
void insert(char *s,int len){
    int nowid=0,pos;//同样的0为根节点,pos记录下一个边的编号。
    for(int i=0;i<len;i++){
        for(pos=head[nowid];pos;pos=g[e].next){
            if(g[pos].c==s[i])break;
        }//去寻找下一个节点的编号
        if(!pos){
            add(nowid,++tot,s[i]);
            pos=cnt;
        }
        nowid=g[pos].to;
    }
    flag[nowid]++;
}

这就是两种trie树的方法啦!QAQ有错请指出


例题:


应用技巧:

其实trie树大多都是运用在AC自动机上面,然后夹杂一些其他的例如DP之类的。

trie树的特殊应用:可以来求异或最大值,方法就是将所有数字转换成二进制插入trie树,这时的trie树就变成了一个二叉树,然后异或最大就按照高位到低位贪心的在trie树上找一遍就好了。

下面附上一个AC自动机学习和trie树学习的神器

Go To Download密码xbj6

posted @ 2018-06-22 17:25  VictoryCzt  阅读(160)  评论(0编辑  收藏  举报