1.11总结

文章介绍了Trie树(字典树)的基本概念,作为字符串集合的存储结构,其主要功能包括插入、查询、统计和求最长公共前缀等。文章还给出了插入和查询操作的示例代码,并结合一个点名错误检查问题展示了Trie树的实际应用。
摘要由CSDN通过智能技术生成

字典树(Trie)

内容来自:www.cnblogs.com

字典树,顾名思义,是关于“字典”的一棵树。即:它是对于字典的一种存储方式(所以是一种数据结构而不是算法)。这个词典中的每个“单词”就是从根节点出发一直到某一个目标节点的路径,路径中每条边的字母连起来就是一个单词。

上图理解:

字典树的功能

根据字典树的概念,我们可以发现:字典树的本质是把很多字符串拆成单个字符的形式,以树的方式存储起来。所以我们说字典树维护的是”字典“。那么根据这个最基本的性质,我们可以由此延伸出字典树的很多妙用。简单总结起来大体如下:

  • 1、维护字符串集合(即字典)。

  • 2、向字符串集合中插入字符串(即建树)。

  • 3、查询字符串集合中是否有某个字符串(即查询)。

  • 4、统计字符串在集合中出现的个数(即统计)。

  • 5、将字符串集合按字典序排序(即字典序排序)。

  • 6、求集合内两个字符串的LCP(Longest Common Prefix,最长公共前缀)(即求最长公共前缀)。

我们可以发现,以上列举出的功能都是建立在“字符串集合”的基础上的。再一次强调,字典树是“字典”的树,一切功能都是“字典”的功能。这也为我们使用字典树的时候提供了一个准则:看到一大堆字符串同时出现,就往哈希和Trie树那边想一下。

插入操作

假如这个字典只包括26个英文字母(暂且都定为小写),那么这个树的深度会由具体单词不一样而定。但是它的广度范围是可以提前确定好的。对于每个节点,广度最大为26。(因为每个节点的下一个字母(即后缀点)只可能是26个字母。)那么我们可以用结构体开好这个“虚拟全树“。然后通过深度迭代向里面尝试加入单词。

我们开一个包含26个后缀指针的结构体。用变量now来表示指向当前节点编号的一个指针,用tot变量表示点的编号。end数组表示当前单词的“目标节点”即单词结尾的那个节点具体是哪个单词的词尾。

那么代码就长成这样:

struct node
{
    int next[27];
}trie[100];
int tot=1;
int insert(char s[],int id)
{
    int now=0;
    int len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int ch=s[i]-'a'+1;
        if(!trie[now].next[ch])
        {
            trie[now].next[ch]=tot;
            tot++;
        }
        now=trie[now].next[ch];
    }
    end[now]=id;
}

查询操作

查询操作和刚刚的思路大同小异,因为我们已经有了一个“虚拟全树”,那么我们还是按深度向下迭代,对于需要查询的字符串的当前字符,如果这个对应的字符指针为空,就说明不含这个单词,直接跳出即可。当我们都迭代完成之后,直接返回end[now]即可。(注意,这里不能直接返回1或true,假如字典中只保存了一个字符串abcdef,而我们查询的是abc,它可以不被跳出地一直迭代到最后,但是它并不是字典中的单词。即,需要考虑字典中单词子串的情况)。

bool search(char s[])
{
    int len=strlen(s);
    int now=0;
    for(int i=0;i<len;i++)
    {
        int ch=s[i]-'a'+1;
        if(!trie[now].next[ch])
            return 0;
    }
    return end[now];
}

P2580 于是他错误的点名开始了

题目背景

XS中学化学竞赛组教练是一个酷爱炉石的人。

他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛 CON900)。

题目描述

这之后校长任命你为特派探员,每天记录他的点名。校长会提供化学竞赛学生的人数和名单,而你需要告诉校长他有没有点错名。(为什么不直接不让他玩炉石。)

输入格式

第一行一个整数 n,表示班上人数。

接下来 n 行,每行一个字符串表示其名字(互不相同,且只含小写字母,长度不超过 50)。

n+2 行一个整数 m,表示教练报的名字个数。

接下来 m 行,每行一个字符串表示教练报的名字(只含小写字母,且长度不超过 50)。

输出格式

对于每个教练报的名字,输出一行。

如果该名字正确且是第一次出现,输出 OK,如果该名字错误,输出 WRONG,如果该名字正确但不是第一次出现,输出 REPEAT。

输入输出样例

输入 #1

5

a

b

c

ad

acd

3

a

a

e

输出 #1

OK

REPEAT

WRONG

说明/提示

  • 对于40% 的数据,n≤1000,m≤2000。

  • 对于70% 的数据,n≤10^4,m≤2×10^4。

  • 对于100% 的数据,n≤10^4,m≤10^5。

#include<stdio.h>
#include<string.h>
int tot=1;

struct note
{
    int next[27];
    int flag;
}trie[1000010];

int insert(char s[])
{
    int now=0;//字典树的编号
    int len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int ch=s[i]-'a'+1;
        if(!trie[now].next[ch])
        {
            trie[now].next[ch]=tot;
            tot++;
        }
        now=trie[now].next[ch];
    }
    trie[now].flag=1;
}

void search(char s[])
{
    int len=strlen(s);
    int now=0;
    for(int i=0;i<len;i++)
    {
        int ch=s[i]-'a'+1;
        if(!trie[now].next[ch])
        {
            printf("WRONG\n");
            return ;
        }
        now=trie[now].next[ch];
    }
   if(trie[now].flag==1)
   {
       printf("OK\n");
       trie[now].flag++;
       return ;
   }
   if(trie[now].flag==0)
       {
           printf("WRONG\n");
           return ;
       }
   else
    {
    printf("REPEAT\n");
   return ;
    }
}
int main()
{
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        char str[60];
        scanf("%s",str);
        insert(str);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        char str[60];
        scanf("%s",str);
        search(str);
    }
}

之前我用原代码时以为end数组记录的就是每个单词的最后一个字母,所以在统计次数时加了一个map数组记录end[now]如果出现就++,但是提交的时候全错了。然后参照了别人的代码,在建造字典树的结构体中加了flag用于统计出现的次序,因为不会用指针,所以在search函数中完成判断输出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值