字典树
[字典树](Trie Tree) 是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串)。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
——百度百科
字典树的时间复杂度和空间复杂度:
1、时间复杂度:插入和查询字符串的复杂度都是O(m),其中m是待插入或查询字符串的长度
2、空间复杂度:有公共前缀的字符串只需要村一次即可,节省了大量空间
基本性质
1、根结点不包含字符,除根结点以外的每个子结点都包含一个字符;
2、从根结点到某个结点,路径上经过字符串连接起来,为该结点对应的字符串;
3、每个结点的所有子结点包含的字符互不相同。
通常在实现的时候会在结点设置一个标志,标志该结点是否为字符串的末尾
字典树的经典应用
1.前缀匹配
我们来看这样一个问题:给出五个字符串,分别是abcd,abd,abc,efg,xyz,现在问在这五个字符串中有多少个字符串是abcdefg的前缀?
方法一:我们会想到暴力的方法,对字符串进行逐个匹配,找到abcdefg的前缀单词。但我们会发现当有n个字符串进行m次询问时,我们的时间复杂度为O(nk),k是字符串的平均长度,但这种操作的效率很低,显然无法让人接受。
方法二:使用字典树,只需要在对重复的字符记录一次(例如:字符串abcd和abd中,a,b字符都只需要记录依次)并对字符串的尾字符进行标记(如下图)。
graph TD
root(( )) --> A1((a))
root --> E4((e))
E4 --> F4((f))
F4 --> G4((g_1))
A1 --> B1((b))
B1 --> C1((c_1))
B1 --> D2((d_1))
C1 --> D1((d_1))
root --> X5((x))
X5 --> Y5((y))
Y5 --> Z5((z_1))
#include <bits/stdc++.h>`
using namespace std;
const int maxn = 1000010;
int trie[maxn][26] = {0};//maxn等于总字符串的长度
int color[maxn];//标记每串字符串的尾字符,依次来记录字符串出现的次数
int k = 0;
/*
插入步骤:
1、记录这串字符串的长度len,并且命名一个变量p指向根
2、 在0~len中开始判断此字符串中的字符是否出现过,如果
没出现过,链接此字符,否则p将下移
3、最后记录这串字符串的尾字符,依次来记录这串字符串出现的次数
*/
void insert(string str)
{
int len = str.length();
int p = 0;
for(int i = 0; i < len; i++)
{
int c = str[i] - 'a';
if(!trie[p][c])
{
trie[p][c] = ++k;
}
p = trie[p][c];
}
color[p]++;
}
/*
查找步骤:
1、记录这串字符串的长度len,并且命名一个变量p指向根,同时命名一个变量用来记录前缀的总个数
2、在0~len中开始判断此字符是否被标记过,如果标记了则 k += color[p],
在判断此字符是否出现过,没有返回k,最后p下指
3、返回 k+color[p]
*/
int search(string str)
{
int len = str.length();
int p = 0;
int k = 0;
for(int i = 0; i < len; i++)
{
int c = str[i] - 'a';
if(color[p]) k += color[p];
if(!trie[p][c]) return k;
p = trie[p][c];
}
return k+color[p];
}
int main()
{
int n, m;
string str;
cin >> n >> m;
for(int i = 0; i < n; i++)
{
cin >> str;
insert(str);
}
while(m--)
{
cin >> str;
cout << search(str) << endl;
}
return 0;
}