字典树(Trie)

时光怂恿深爱的人放手 提交于 2020-02-12 22:56:28

一、基本概念:
  字典树(Trie)是一种用于实现字符串快速检索的多叉树结构。字典树的每一个结点都拥有若干个字符指针,若在插入或检索字符串时扫描到一个字符 c,就沿着当前结点的 c 字符指针,走向该指针指向的结点。
1、初始化
  一棵空字典树仅包含一个根节点,该点的字符指针均指向空。
2、插入操作
  当需要插入一个字符串 str 时,我们另一个指针 p 指向根节点。人后,依次扫描 s 中的每一个字符 c :
  (1)若 p 的 c 字符指针指向一个已经存在的结点 q,则令 p = q
  若 p 的 c 字符指针指向空,则新建一个结点 q,令 p = q
  当 s 中的字符扫描完毕时,在当前结点 p 上标记它是一个字符串的尾部

int trie[maxn][26],tot=1;
bool nd[maxn];
void _insert(char str[])
{
    int p = 1;
    for(int k=0;str[k];k++){
        int ch = str[k]-'a';
        if(trie[p][ch]==0)  trie[p][ch] = ++tot;
        p = trie[p][ch];
    }
    nd[p] = true;
}

2、检索操作
  当需要检索一个字符串 str 在字典树是否存在时,我们令一个指针 p 指向根节点,然后,依次扫描 str 中的每个字符 c :
  (1)若 p 的 c 字符指针指向空,则说明 str 没有插入过 字典树,结束检索
  (2)若 p 的 c 字符指针指向一个已经存在的结点 q,则令 p = q
  当 s 中的字符扫描完毕时,若当前结点 p 被标记为一个字符串的尾部,则说明 str 在字典树中存在,否则不存在

bool _search(char str[])
{
    int p = 1;
    for(int k=0;str[k];k++){
        p = trie[p][str[k]-'a'];
        if(p==0)    return false;
    }
    return nd[p];
}

代码比较简单,但是我们进一步想统计前缀出现的次数怎么办?那就开一个 sum 数组,表示某节点被访问过的次数。我们知道对于每一个前缀单词的插入,只要出现过这个前缀,那么总是要遍历一次从根节点到这个前缀单词的终节点路径中所有的节点,在遍历每一个节点的时候,我们都让此节点的sum计数数组加一即可。而对于某个前缀出现的次数,我们最后只需要返回此前缀单词最后一个字符对应的sum值即可。代码如下:

int trie[maxn][26],tot;
int sum[maxn];
void _insert(char str[])
{
    int p = 0;
    for(int k=0;str[k];k++){
        int ch = str[k]-'a';
        if(trie[p][ch]==0)  trie[p][ch] = ++tot;
        p = trie[p][ch];
        sum[p]++;
    }
}
int _search(char str[])
{
    int p = 0;
    for(int k=0;str[k];k++){
        p = trie[p][str[k]-'a'];
        if(sum[p]==0)    return 0;
    }
    return sum[p];
}

二、例题
1、模板题目:HDU 1251 统计难题
注意控制输入输出即可,其余模板。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<map>
#include<set>
#include<tr1/unordered_set>
#define ll long long
#define ld long double
#define ull unsigned long long
using namespace std;
using namespace tr1;
const int maxn =  1000010;
int trie[maxn][26],tot;
int sum[maxn];
char a[24];
void _insert(char str[])
{
    int p = 0;
    for(int k=0;str[k];k++){
        int ch = str[k]-'a';
        if(trie[p][ch]==0)  trie[p][ch] = ++tot;
        p = trie[p][ch];
        sum[p]++;
    }
}
int _search(char str[])
{
    int p = 0;
    for(int k=0;str[k];k++){
        p = trie[p][str[k]-'a'];
        if(sum[p]==0)    return 0;
    }
    return sum[p];
}
int main(void)
{
    while(gets(a),strlen(a)!=0){
        _insert(a);
    }
    while(~scanf("%s",a)){
        printf("%d\n",_search(a));
    }
    return 0;
}

2、AcWing 142. 前缀统计
给定N个字符串S1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1~SN中有多少个字符串是T的前缀。

反向提问,那么我们就把每一个以 p 的 c 字符指针为结尾的字符,用 nd 数组记入一下个数。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<map>
#include<set>
#include<tr1/unordered_set>
#define ll long long
#define ld long double
#define ull unsigned long long
using namespace std;
using namespace tr1;
const int maxn =  1000010;
int trie[maxn][26];
int nd[maxn],tot;
char a[maxn];

void _insert(char str[])
{
    int p = 0;
    for(int k=0;str[k];k++){
        int ch = str[k]-'a';
        if(trie[p][ch]==0)  trie[p][ch] = ++tot;
        p = trie[p][ch];
    }
    nd[p]++;
}
int _search(char str[])
{
    int ans = 0,p = 0;
    for(int k=0;str[k];k++){
        p = trie[p][str[k]-'a'];
        if(p==0)    return ans;
        ans += nd[p];
    }
    return ans;
}
int main(void)
{
    int n,m;
    scanf("%d%d",&n,&m);
    while(n--){
        scanf("%s",a);
        _insert(a);
    }
    while(m--){
        scanf("%s",a);
        printf("%d\n",_search(a));
    }
    return 0;
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!