Trie树(字典树、前缀树) (小白整理)

此处大佬文章引路:

 

字典树(Trie)详解 - Seaway-Fu - 博客园 (cnblogs.com)

Trie树(Prefix Tree)介绍_神奕的专栏-CSDN博客_prefix tree

目录

0.初遇字典树

1.用处:

2.性质:

3.作用扩展:

4.优缺点:

5.代码实现(建树、查询):


0.初遇字典树

Trie树,又叫字典树前缀树(Prefix Tree)单词查找树 或 键树,是一种多叉树结构。空间换时间:开的空间大,但查询效率快

 可以发现,这棵字典树用边来代表字母,而从根结点到树上某一结点的路径就代表了一个字符串。举个例子,1→4→8→12 表示的就是字符串 caa。(图片摘自OI Wiki)

宽度:范围(例:小写英文字母->26)

深度:最长的字母长度加1(root根节点独占顶层)

1.用处:

维护字符串,存储 字典 的一种数据结构。

2.性质:

1根节点不包含字符,除根节点外的每一个子节点都包含一个字符。

2从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。

3每个节点的所有子节点包含的字符互不相同。

注:

<1>在建树的时候,每个结点放个标记,看它能不能构成单词。

<2>两个有公共前缀的关键字,在Trie树中前缀部分路径相同,所以又叫前缀树。

3.作用扩展:

(1)字符串检索:

检索/查询功能是Trie树最原始的功能。思路就是从根节点开始一个一个字符进行比较

<1>如果沿路比较,发现不同的字符,则表示该字符串在集合中不存在。

<2>如果所有的字符全部比较完并且全部相同,还需判断最后一个节点的标志位(标记该节点是否代表一个关键字)。

struct trie_node

{

    bool trie_flag;   // 标记该节点是否代表一个关键字/单词

    trie_node_next [26]; // 各个子节点

};

(2) 词频统计:Trie树常被搜索引擎系统用于文本词频统计。

struct trie_node

{

    int words_count;   // 记录该节点代表的单词的个数

    trie_node_next [26]; // 各个子节点

};

思路:为了实现词频统计,我们修改了节点结构,用一个整型变量count来计数。对每一个关键字执行插入操作,若已存在,计数加1,若不存在,插入后count置1。

(3) 字符串排序:

思路:遍历一次所有关键字,将它们全部插入trie树,树的每个结点的所有儿子很显然地按照字母表排序,然后先序遍历输出Trie树中所有关键字即可。

(4)前缀匹配:

例如:找出一个字符串集合中所有以ab开头的字符串。我们只需要用所有字符串构造一个trie树,然后输出以a->b->开头的路径上的关键字即可。

trie树前缀匹配常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。

(5)如后缀树,AC自动机等。

4.优缺点:

优点:插入、查询O(n) (n:点的个数) 的复杂度,不用求hash值,字典序排序。

缺点:hash函数很好时,trie树的查找效率比不过哈希搜索,空间消耗比较大。

5.代码实现(建树、查询):

代码如下(改自OI Wiki):

struct trie {

  int next[100000][26], cnt; //前一维表示存的结点,后一维表示宽度,如果想添加其它功能,可以把int型改为结构体类型

  bool exist[100000];  //判断此结点能否构成单词

  void insert(char *s, int l) {  // 插入字符串s,l为字符串s的长度

    int p = 0;

    for (int i = 0; i < l; i++) {

      int c = s[i] - 'a';    //这里是把char型换成int型0~25储存起来

      if (!next[p][c]) next[p][c] = ++cnt;  // 如果没有,就添加结点

      p = next[p][c];

    }

    exist[p] = 1;

  }

  bool find(char *s, int l) {  // 查找字符串  //可根据具体情况返回bool/int型

    int p = 0;

    for (int i = 0; i < l; i++) {

      int c = s[i] - 'a';

      if (!next[p][c]) return 0;

      p = next[p][c];

    }

    return exist[p];

  }};

板子题:P2580 于是他错误的点名开始了 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:此例题就需要根据find返回的int型判断点到的次数,将exist数组类型改为int型

注:next数组前一维是所有字符串中字母的总个数,如字符串长度100以内,个数1e5,那nxet[N][26]中的前一维需要开到1e6~1e7(有前缀重叠情况),即 N的大小<=字符串长度*字符串个数

AC代码如下:

/*
trie树 板子题 洛谷P2580 于是他错误的点名开始了
https://www.luogu.com.cn/problem/P2580
*/
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N=1e6+10;

int n,m;
char s[100];

struct trie{
    int next[N][26],cnt=0;   //前一维存的结点,后一位宽度
    int exist[N];      //判断此结点能否构成单词

    void insert(char *s){
        int p=0;
        //printf("strlen(s)=%d\n",strlen(s));
        for(int i=0;i<strlen(s);i++)
        {
            int c=s[i]-'a';
            if(!next[p][c])
            {
                next[p][c]=++cnt;
            }
            p=next[p][c];
        }
        exist[p]=1;
    }
    int find(char *s){
        int p=0;
        for(int i=0;i<strlen(s);i++)
        {
            int c=s[i]-'a';
            if(!next[p][c])
            {
                return 0;
            }
            p=next[p][c];
        }
        if(exist[p]==1)
        {
            exist[p]=2;
            return 1;
        }
        else
            return 2;
    }
}trie_tree;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf(" %s",s);
        trie_tree.insert(s);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf(" %s",s);
        if(trie_tree.find(s)==1)
        {
            printf("OK\n");
        }
        else if(trie_tree.find(s)==2)
        {
            printf("REPEAT\n");
        }
        else
        {
            printf("WRONG\n");
        }
    }
    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哑笙

今天星期四,v我50听重生故事

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值