学习笔记:字典树

字典树

引入

字典树(又称 Trie 树)是一种树形数据结构,可以用来存储和查询字符串。例如,waterwishwintietired 这几个字符串可以用这样一棵字典树存储:

实现

Trie 树每一个字符占据树上的一个节点,拥有相同前缀的字符串可以共用部分节点。我们令起始点为特殊点(不存储字符),所有字符串均从深度为 2(起始点深度为 1)的节点开始存储。对于每一个节点,我们都打上标记:

    for(int i = 1 ; i <= n ; i ++){
        cin >> s + 1;root = 1; // 读入一个将要插入 Trie 树的字符串,并将指针初始化到起始点
        for(int j = 1 ; s[j] != 0 ; j ++){ // 将字符串从头到尾扫一遍
            ch = s[j] - 'a'; // 计算字符对应的 ASCII 码
            if(trie[root][ch] == 0) // 若该节点未加入 Trie 树
                trie[root][ch] = ++tot; // 将节点加入 Trie 树
            root = trie[root][ch]; // 将指针指向下一节点
        }
        tag[root] = 1; // 标记该字符串存在
    }

不难看出,Trie 树的具体实现是一个二维数组,插入字符串时将对应节点打上标记。

至于查询的话,依然是对着要查询的字符串从头到尾扫一遍,边扫边在 Trie 树上匹配。

    for(int i = 1 ; i <= m ; i ++){
        cin >> s + 1;root = 1; // 读入要查询的字符串,并将指针初始化到起始点
        for(int j = 1 ; s[j] != 0 ; j ++){ // 将字符串从头到尾扫一遍
            ch = s[j] - 'a'; // 计算字符对应的 ASCII 码
            root = trie[root][ch]; // 将指针指向下一节点
            if(root == 0)break; // 若该节点为字节点,则直接停止匹配,否则继续
        }
        if(tag[root] == 1){ // 若字符串存在且仅插入一次
            cout << "OK" << endl;tag[root] = 2;
        }else if(tag[root] == 2)cout << "REPEAT" << endl; // 若字符串存在且插入多次
        else cout << "WRONG" << endl; // 若字符串不存在
    }

在查询时,通常采取边扫边匹配的做法,匹配完之后再查询标记数组所记录的插入次数。

字典树是一种空间换时间的数据结构,我们牺牲了 ×× 的空间,但可以用 O(n) 的时间查询,其中 n 为查询的前缀或字符串的长度。

看题看题:

于是他错误的点名开始了

简要题意

给定 n 个字符串,有 m 次询问。对于每一个询问存在三种情况:

  1. 若询问的字符串出现过一次,输出 OK
  2. 若询问的字符串从未出现过,输出 WRONG
  3. 若询问的字符串出现过多次,输出 REPEAT

思路

Trie 树板子题。

直接将前 n 个字符串加入 Trie 树中再分情况处理每一个询问即可。

#include <iostream>
#define MAXN 10005
#define MAXM 100005
#define MAXL 55
using namespace std;
int n, m, tot = 1, root, ch;
char s[MAXL];
int trie[MAXM << 2][26], tag[MAXM << 2]; // 记得开四倍空间
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 1 ; i <= n ; i ++){
        cin >> s + 1;root = 1; // 读入一个将要插入 Trie 树的字符串,并将指针初始化到起始点
        for(int j = 1 ; s[j] != 0 ; j ++){ // 将字符串从头到尾扫一遍
            ch = s[j] - 'a'; // 计算字符对应的 ASCII 码
            if(trie[root][ch] == 0) // 若该节点未加入 Trie 树
                trie[root][ch] = ++tot; // 将节点加入 Trie 树
            root = trie[root][ch]; // 将指针指向下一节点
        }
        tag[root] = 1; // 标记该字符串存在
    }
    cin >> m;
    for(int i = 1 ; i <= m ; i ++){
        cin >> s + 1;root = 1; // 读入要查询的字符串,并将指针初始化到起始点
        for(int j = 1 ; s[j] != 0 ; j ++){ // 将字符串从头到尾扫一遍
            ch = s[j] - 'a'; // 计算字符对应的 ASCII 码
            root = trie[root][ch]; // 将指针指向下一节点
            if(root == 0)break; // 若该节点为字节点,则直接停止匹配,否则继续
        }
        if(tag[root] == 1){ // 若字符串存在且仅插入一次
            cout << "OK" << endl;tag[root] = 2;
        }else if(tag[root] == 2)cout << "REPEAT" << endl; // 若字符串存在且插入多次
        else cout << "WRONG" << endl; // 若字符串不存在
    }
    return 0;
}
posted @   tsqtsqtsq  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示