小白学算法(12)单词查找树

164 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情

Trie树(单词查找树)

1.模板介绍

835. Trie字符串统计 - AcWing题库

维护一个字符串集合,支持两种操作:

  1. I x 向集合中插入一个字符串 xx;
  2. Q x 询问一个字符串在集合中出现了多少次。

共有 N 个操作,所有输入的字符串总长度不超过 1e5,字符串仅包含小写英文字母

高效地存储和查找字符串集合的数据结构

单词结尾进行标记

2.模板详解

1)初始化

首先我们要考虑如何构建整个数,这里选择的是二维数组,将其看成每一行看出当前字符串的每一位,每一列就表示最多有26个字母,有26个路径可以走.

并且考虑到如何判断路径是否走到头了,并且判断有多少个相同的路径,所以再使用一个一位数组进行标识结尾

这里使用数组模拟树,所以需要一个节点变量,这个方法参考前面的数组模拟单链表等数据结构 小白学算法(8)区间合并+单链表 - 掘金 (juejin.cn)

// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
int son[N][26],ctn[N],idx;

2)插入

我们是将字符串变成一个树,要进行每层的遍历,并且判断字符的位置,首先需要进行一个ASCII码的转换。然后判断当前位置是否存在字符串xx位,如果该位置不存在就需要进行创建,并且进入下一层。最后遍历完字符路径后,需要进行一个字符结尾的标记,一是标记该字符串在树中存在,二是标记字符串出现的次数

void insert(char *str)
{
    int p = 0;  //类似指针,指向当前节点
    for(int i = 0; str[i]; i++)
    {
        //将字母转化为数字
        int u = str[i] - 'a'; 
         //该节点不存在,创建节点,其值为下一个节点位置
        if(!son[p][u]) son[p][u] = ++idx;
        //使“p指针”指向下一个节点位置
        p = son[p][u];  
    }
    cnt[p]++;  //结束时的标记,也是记录以此节点结束的字符串个数
}

3)查找

查找与插入的方法一致,不过只是在判断节点是否存在和遍历字符串结束时直接范围标记数组的值即可

int query(char *str)
{
    int p = 0;
    for(int i = 0; str[i]; i++)
    {
        int u = str[i] - 'a';
        if(!son[p][u]) return 0;  //该节点不存在,即该字符串不存在
        p = son[p][u]; 
    }
    return cnt[p];  //返回字符串出现的次数
}
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

int son[N][26],ctn[N],idx;
// 静态数组构造结构时,idx用来分配新的节点
void insert(char *str)
{
    int p = 0;  //类似指针,指向当前节点
    for(int i = 0; str[i]; i++)
    {
        //将字母转化为数字
        int u = str[i] - 'a'; 
         //该节点不存在,创建节点,其值为下一个节点位置
        if(!son[p][u]) son[p][u] = ++idx;
        //使“p指针”指向下一个节点位置
        p = son[p][u];  
    }
    cnt[p]++;  //结束时的标记,也是记录以此节点结束的字符串个数
}
int query(char *str)
{
    int p = 0;
    for(int i = 0; str[i]; i++)
    {
        int u = str[i] - 'a';
        if(!son[p][u]) return 0;  //该节点不存在,即该字符串不存在
        p = son[p][u]; 
    }
    return cnt[p];  //返回字符串出现的次数
}

3.另类字典树

字典树不仅能存储字符,还可以存储数的二进制等一切,有限定范围的内容

143. 最大异或对 - AcWing题库

在给定的 N个整数 A1,A2……AN中选出两个进行 xor(异或)运算,得到的结果最大是多少?

这里选择两个数进行运算很显然可以使用暴力,两个for循环进行遍历运算,但可想很慢。

而我们先思考异或运算是怎么执行的

xor(异或)

1^1=0 1^0=1 0^0=0

计算机对于异或运算都是先将两个数进行转换二进制数进行逐位运算

想到分开逐位运算,我们可以使用单词查找数的思想,进行二进制数的存储,这次每一位就不是26个字母了,而是1/0 两个数

1)初始化

先看初始化,与字符查找一致,但注意范围

const int N=1e5+10;
//int数二进制最大范围是32位,我们是0下标,所以注意存储的大小是31*N  +10防止边界
const int M=31*N+10;
int son[M][2],idx;
int a[N];

2)插入

字符串查找数字符下标是使用ASCII码,我们则是十进制转为二进制当前位即可。求某一位数在前面位运算介绍过小白学算法(5)之位运算 - 掘金 (juejin.cn)

void insert(int x)
{
    int p=0;
    for(int i=30;i>=0;i--)
    {
        //求某一位的数
        int u=x>>i&1;
        if(!son[p][u])son[p][u]=++idx;
        p=son[p][u];
    }
}

3)查找

查找异或最大的数,我们只需要查找当前位的相反数

比如我们查找101 异或最大数

第一次是1 异或最大的是0 所以我们往0那条路径走

第二次是0 异或最大的是1 所以我们往1那条路径走 以此类推

这里我们会用到res=res*2+u 用十进制意思就是 第一层是8 找到第二层2了,那么恢复为原数就是8*10+2=82

int search(int x)
{
    //这里用res存储异或数
    int p=0,res=0;
    for(int i=30;i>=0;i--)
    {
        int u=x>>i&1;
        //找到异或最大的,那么就是当前位不一样的
        if(son[p][!u])
        {
            p=son[p][!u];
            //就加上反的那一个
            //res=res*2+1 异或最大就为1
            res=res*2+!u;
        }else
        {
            p=son[p][u];
            res=res*2+u;
        }
    }
  	//然后返回异或最大的
    return res^x;
}