LogoCSP Wiki By Yundou
5 String

Trie树

定义

字典树,英文名 trie。顾名思义,就是一个像字典一样的树。

引入

如下图:

图片描述

可以发现,这棵字典树用边来代表字母,而从根结点到树上某一结点的路径就代表了一个字符串。举个例子,148121\to4\to 8\to 12 表示的就是字符串 caa

trie 的结构非常好懂,我们用 δ(u,c)\delta(u,c) 表示结点 uucc 字符指向的下一个结点,或着说是结点 uu 代表的字符串后面添加一个字符 cc 形成的字符串的结点。(cc 的取值范围和字符集大小有关,不一定是 0260\sim 26。)

有时需要标记插入进 trie 的是哪些字符串,每次插入完成时在这个字符串所代表的节点处打上标记即可。

图片描述

实现

放一个结构体封装的模板:

struct trie {
  int nex[100000][26], cnt;
  bool exist[100000];  // 该结点结尾的字符串是否存在
 
  void insert(char *s, int l) {  // 插入字符串
    int p = 0;
    for (int i = 0; i < l; i++) {
      int c = s[i] - 'a';
      if (!nex[p][c]) nex[p][c] = ++cnt;  // 如果没有,就添加结点
      p = nex[p][c];
    }
    exist[p] = true;
  }
 
  bool find(char *s, int l) {  // 查找字符串
    int p = 0;
    for (int i = 0; i < l; i++) {
      int c = s[i] - 'a';
      if (!nex[p][c]) return 0;
      p = nex[p][c];
    }
    return exist[p];
  }
};

应用

检索字符串

字典树最基础的应用——查找一个字符串是否在「字典」中出现过。

题目于是他错误的点名开始了

题面

给你 nn 个名字串,然后进行 mm 次点名,每次你需要回答「名字不存在」、「第一次点到这个名字」、「已经点过这个名字」之一。

1n1041\le n\le 10^41m1051\le m\le 10^5,所有字符串长度不超过 5050

参考代码

#include <cstdio>
using namespace std;
constexpr int N = 500010;
 
char s[N];
int n, m, ch[N][26], tag[N], tot = 1;
 
int main() {
  scanf("%d", &n);
 
  for (int i = 1; i <= n; ++i) {
    scanf("%s", s + 1);
    int u = 1;
    for (int j = 1; s[j]; ++j) {
      int c = s[j] - 'a';
      // 如果这个节点的子节点中没有这个字符,添加上并将该字符的节点号记录为++tot
      if (!ch[u][c]) ch[u][c] = ++tot;
      u = ch[u][c];  // 往更深一层搜索
    }
    tag[u] = 1;  // 最后一个字符为节点 u 的名字未被访问到记录为 1
  }
 
  scanf("%d", &m);
 
  while (m--) {
    scanf("%s", s + 1);
    int u = 1;
    for (int j = 1; s[j]; ++j) {
      int c = s[j] - 'a';
      u = ch[u][c];
      if (!u) break;  // 不存在对应字符的出边说明名字不存在
    }
    if (tag[u] == 1) {
      tag[u] = 2;  // 最后一个字符为节点 u 的名字已经被访问
      puts("OK");
    } else if (tag[u] == 2)  // 已经被访问,重复访问
      puts("REPEAT");
    else
      puts("WRONG");
  }
 
  return 0;
}

例题:

On this page