0x16 Tire

参考链接:https://www.cnblogs.com/TheRoadToTheGold/p/6290732.html

题目链接:https://www.acwing.com/problem/content/description/144/

一、引入

字典是干啥的?查找字的。

字典树自然也是起查找作用的。查找的是啥?单词。

看以下几个题:

1、给出n个单词和m个询问,每次询问一个单词,回答这个单词是否在单词表中出现过。

答:简单!map,短小精悍。

好。下一个

2、给出n个单词和m个询问,每次询问一个前缀,回答询问是多少个单词的前缀。

答:map,把每个单词拆开。

judge:n<=200000,TLE!

这就需要一种高级数据结构——Trie树(字典树)

二、原理

在本篇文章中,假设所有单词都只由小写字母构成

对cat,cash,app,apple,aply,ok 建一颗字典树,建成之后如下图所示

由此可以看出:

1、字典树用边表示字母

2、有相同前缀的单词公用前缀节点,那我们可以的得出每个节点最多有26个子节点(在单词只包含小写字母的情况下)

3、整棵树的根节点是空的。为什么呢?便于插入和查找,这将会在后面解释。

4、每个单词结束的时候用一个特殊字符表示,图中用的‘′,那么从根节点到任意一个‘′,那么从根节点到任意一个‘’所经过的边的所有字母表示一个单词。

三、基本操作

A、insert,插入一个单词

1.思路

从图中可以直观看出,从左到右扫这个单词,如果字母在相应根节点下没有出现过,就插入这个字母;否则沿着字典树往下走,看单词的下一个字母。

这就产生一个问题:往哪儿插?计算机不会自己选择位置插,我们需要给它指定一个位置,那就需要给每个字母编号。

我们设数组trie[i][j]=k,表示编号为i的节点的第j个孩子是编号为k的节点。

什么意思呢?

这里有2种编号,一种是i,k表示节点的位置编号,这是相对整棵树而言的;另一种是j,表示节点i的第j的孩子,这是相对节点i而言的。

不理解?看图

还是单词cat,cash,app,apple,aply,ok

我们就按输入顺序对其编第一种号,红色表示编号结果。因为先输入的cat,所以c,a,t分别是1,2,3,然后输入的是cash,因为c,a是公共前缀,所以从s开始编,s是4,以此类推。

注意这里相同字母的编号可能不同

第二种编号,相对节点的编号,紫色表示编号结果。

因为每个节点最多有26个子节点,我们可以按他们的字典序从0——25编号,也就是他们的ASCLL码-a的ASCLL码。

注意这里相同字母的编号相同

实际上每个节点的子节点都应该从0编到——25,但这样会发现许多事根本用不到的。比如上图的根节点应该分出26个叉。节约空间,用到哪个分哪个。

这样编号有什么用呢?

回到数组trie[i][j]=k。 数组trie[i][j]=k,表示编号为i的节点的第j个孩子是编号为k的节点。

那么第二种编号即为j,第一种编号即为i,k



Trie(字典树)是种用于实现字符串快速检索的多叉树结构。Trie的每个节点都拥有若干个字符指针,若在插入或检索字符串时扫描到一个字符c, 就沿着当前节点的c字符指针,走向该指针指向的节点。下面我们来详细讨论Trie的基本操作过程。初始化
一棵空Trie 仅包含一个根节点,该点的字符指针均指向空。

插入
当需要插入一个字符串S时,我们令一个指针P起初指向根节点。然后,依次扫描S中的每个字符c:
1.若P的c字符指针指向一个已经存在的节点Q,则令P=Q.
2.若P的c字符指针指向空,则新建一个节点Q, 令P的C字符指针指向Q,然后令P=Q。

当S扫描完后,在当前节点P上标记他是一个末尾字符串。

检索

当需要检索一个字符串S在Trie中是否存在时,我们令一个指针P起初指向根节点,然后依次扫描S中的每个字符c:

1.若P的c字符指针指向空,则说明S没有被插入过Trie,结束检索。

2.若P的c字符指针指向一个已经存在的节点Q,则令P=Q.

当S中的字符扫描完毕时,若当前节点p被标记为一个字符串的末尾,则说明在Trie中存在,否则说明s没有被插入过Trie。

在上图所示的例子中,需要插入和检索的字符串都由小写字母构成,所以Trie树每个节点具有26个字符指针,分别为a到z。上图展示了在一棵空 Trie中依次插人“cab"“cos”“car‘”“‘cat”“cate" 和“rain" 后的Trie 的形态,灰色标记了单词的末尾节点。可以看出在Trie中,字符数据都体现在树的边(指针)上,树的节点仅保存一些额外信息,例如单词结尾标记等。其空间复杂度是0(NC), 其中N是节点个数,c是字符集的大小。

void insert(char *s)//插入单词s
{
    len=strlen(s);//单词s的长度
    root=0;//根节点编号为0
    for(int i=0;i<len;i++)
    {
        int id=s[i]-‘a‘;//第二种编号
        if(!trie[root][id])//如果之前没有从root到id的前缀
            trie[root][id]=++tot;//插入,tot即为第一种编号
        root=trie[root][id];//顺着字典树往下走
    }
    end[root]=true;
}

  


bool find(char *s)
{
    len=strlen(s);
    root=0;//从根结点开始找
    for(int i=0;i<len;i++)
    {
        int x=s[i]-‘a‘;//
        if(trie[root][x]==0)   return false;//以root为头结点的x字母不存在,返回0
        root=trie[root][x];//为查询下个字母做准备,往下走
    }
    return true;//找到了
}

前缀统计

把这N个字符串插入一棵Trie树,Trie 树的每个节点上存储一个整数cnt, 记录该节点是多少个字符串的末尾节点。(为了处理插入重复字符串的情况,这里要记录个数,而不能只做结尾标记)
对于每个询问,在Trie树中检索T,在检索过程中累加途径的每个节点的cnt值,就是该询问的答案。

#include<iostream>
#include<string.h>
using namespace std;
const int SIZE=100050;
int trie[SIZE][26],tot=1;
int END[SIZE];
int cnt;
void insert(const char* str){
    int len=strlen(str),p=1;
    for (int k = 0; k < len; ++k) {
        int ch=str[k]-‘a‘;
        if(trie[p][ch]==0)
            trie[p][ch]=++tot;
        p=trie[p][ch];
    }
    END[p]++;
}

int search(const char* str){
    cnt=0;
    int len=strlen(str),p=1;
    for (int k = 0; k < len; ++k) {
        int ch=str[k]-‘a‘;
        p=trie[p][ch];
        if(p==0)
            return cnt;
        cnt+=END[p];
    }
    return cnt;
}

int main(){
    int n,m;
    cin>>n>>m;
    while(n--){
        string s;
        cin>>s;
        insert(s.c_str());
    }
    while(m--){
        string s;
        cin>>s;
        search(s.c_str());
        cout<<cnt<<endl;
    }
    return 0;
}

  

原文地址:https://www.cnblogs.com/clarencezzh/p/10776849.html

时间: 2024-10-08 18:49:58

0x16 Tire的相关文章

0x16 Tire之最大的异或对

我们考虑所有的二元组(i,j)且i<j,那么本题的目标就是在其中找到Ai xorAj的最大值.也就是说,对于每个i(1≤i≤N),我们希望找到一个j(1<j<i),使AixorAj最大,并求出这个最大值. 我们可以把每个整数看作长度为32的二进制01串(数值较小时在前边补0),并且把A1~Ai-1对应的32位二进制串插入一棵Trie 树(其中最低二进制位为叶子节点).接下来,对于Ai对应的32位二进制串,我们在Trie中进行一次与检索类似的过程,每一步都尝试沿着“与Ai的当前位相反的字符

HDU 4825 tire树

Xor Sum Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Others)Total Submission(s): 2505    Accepted Submission(s): 1076 Problem Description Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Ze

HDOJ1251-统计难题(Tire)

Problem Description Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀). Input 输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串. 注意:本题只有一组测试数据,处理到文件结束. Output 对于每个提

Ancient Printer(tire树)

Ancient Printer Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)Total Submission(s): 1511    Accepted Submission(s): 748 Problem Description The contest is beginning! While preparing the contest, iSea wanted to pri

HDU 1251 统计难题(Tire tree)

统计难题 Problem Description Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀). Input 输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串. 注意:本题只有一组测试数据,处理到文件结束. Output

Tire树

Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种. 典型应用是用于统计和排序大量的字符串(但不仅限于字符串), 所以经常被搜索引擎系统用于文本词频统计. 优点 利用字符串的公共前缀来节约存储空间,最大限度的减少无谓的字符串比较,查询效率比哈希表高. 比如说我们想储存3个单词,sky.skyline.skymoon.如果只是单纯的按照以前的字符数组存储的思路来存储的话,那么我们需要定义三个字符串数组.但是如果我们用字典树的话,只需要定义一个树就可以了.在这里我们就可以看到字典树

hdu 1251(tire 树的简单应用)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1251 题意:给你多个字符串,求以某个字符串为前缀的字符串数量. 思路:简单的tire数应用,在tire的数据结构中增加一个存储到当前节点字符串出现的次数,在插入的过程中维护即可. code: 1 #include <cstdio> 2 #include <cstring> 3 const int KIND = 26; 4 struct TireNode 5 { 6 int num; /

UVa 1401 (Tire树) Remember the Word

d(i)表示从i开始的后缀即S[i, L-1]的分解方法数,字符串为S[0, L-1] 则有d(i) = sum{ d(i+len(x)) | 单词x是S[i, L-1]的前缀 } 递推边界为d(L) = 1,代表空串. 将前n个单词构造一颗Tire树,在树中查找后缀的过程中遇到一个单词节点就代表找到一个状态转移中的x 1 #include <cstdio> 2 #include <cstring> 3 4 const int maxnode = 400000 + 10; 5 co

【HUD-5790】Prefix (主席树+tire)

似乎是归队赛的最后一道题. 由于当时以为是公共字串所以没写555555,其实是求公共前缀. 做法是建立tire,把tire上的点编号看成是值,查询第l到第r个字符串的区间内不重复的值的个数.建立主席树维护即可 #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> #define rep(i,l,r) for(int i=l;i<=r;i++) #define dow