37.字典树
一、简介
字典树,英文名为 Trie,又称单词查找树,它是一种特殊的数据结构,用于处理字符串的一种树形结构。顾名思义,字典树在使用的时候类似于在字典中查单词,从第一个字符开始遍历,在 Trie 中按层往下查找,查找效率可以达到 O(n)O(n)O(n),nnn 为查找字符串的长度。
Trie 的功能非常强大,可以支持对于字符串的各种操作,包括插入、删除、修改和查询操作。Trie一边用来处理字符串的快速检索,字符串的快速排序与去重,文本的词频统计等问题。Trie 的构建利用字符串的公共前缀,逐层建立起一棵多叉树。下图是一个基础的例子:
从这张图中我们不难看出 Trie 具有以下特点:
- Trie 的根结点上不存储字符,其余结点上存且只存一个字符。
- 从根结点出发,到某一结点上经过的字符,即是该结点对应的前缀。
- 每个结点的孩子结点存储的字符各不相同。
- Trie 牺牲空间来换取时间,当数据量很大时,会占用很大空间。如果字符串均由小写字母组成,则每个结点最多会有 262626 个孩子结点,则最多会有 26n26n26n 个用于存储的结点,nnn 为字符串的长度总和。
二、基本运用
任何一个良好的数据结构都应该满足增加、删除、修改和查询四大功能,下面我们从这些方面来讲解。我们假设题目的需求是:求出当前所查询的字符串是多少前面所给字符串的前缀。我们以此为基础来介绍字典树的基本构造方法,需要注意对于每道题目各种功能的实现也略有不同。
1.初始化
- 在一开始需要将
trie
数组初始化为 −1-1−1,表示该节点从未使用过。 tot
变量用于表示整个字典树中存在多少节点。
void init()
{memset(trie, -1, sizeof(trie));memset(sum, 0, sizeof(sum));tot = 0;
}
2.插入
- 在插入字符串的时候,我们首先从字典树的根节点(编号为 000)开始。
- 遍历整个字符串,因为字典树以数组的方式存储,所以需要通过某种方式将符号对应到数字。这里假设所有字母都是小写,因此使用了
str[i]-'a'
的方式进行转换。 trie[p][ch] == -1
表示该节点还没有被创建过,所以新建节点++tot
。p = trie[p][ch]
表示为进入当前字符串前 iii 个字符的对应子节点。sum[p]++
用于记录以当前这样的前 iii 个字符为前缀的字符串有多少个
void insert(string str)
{ll len = str.size(), p = 0;for(ll i = 0; i < len; i++){ll ch = str[i] - 'a';if(trie[p][ch] == -1)trie[p][ch] = ++tot;p = trie[p][ch];sum[p]++;}
}
3.删除
- 因为
tot
的数量已经改变,所以最好不好直接删除节点 - 我们只需要将
sum[p]
的计数情况做一些修改即可
void erase(string str)
{ll len = str.size(), p = 0;for(ll i = 0; i < len; i++){ll ch = str[i] - 'a';if(trie[p][ch] == -1)return;p = trie[p][ch];sum[p]--;}
}
4.查询
- 根据字符串在字典树中的位置依次递推直到结尾,最后返回当前的计数情况即可。
ll search(string str)
{ll len = str.size(), p = 0;for(ll i = 0; i < len; i++){ll ch = str[i] - 'a';if(trie[p][ch] == -1)return 0;p = trie[p][ch];}return sum[p];
}
三、作业
1.黄题
P1481 魔族密码
P2580 于是他错误的点名开始了
P8306 【模板】字典树
UVA11362 Phone List
2.绿题
CF706D Vasiliy’s Multiset