zoukankan      html  css  js  c++  java
  • 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;//找到了
    }
    

    字典树的完整代码

    #include <iostream>
    using namespace std;
    
    const int N = 1e5 + 10;
    int son[N][26]; // 其中存放的是:子节点对应的idx。其中son数组的第一维是:父节点对应的idx,第第二维计数是:其直接子节点('a' - '0')的值为二维下标。
    int cnt [N];    // 以“abc”字符串为例,最后一个字符---‘c’对应的idx作为cnt数组的下标。数组的值是该idx对应的个数。
    int idx;        // 将该字符串分配的一个树结构中,以下标来记录每一个字符的位置。方便之后的插入和查找。内存计数器,内存用到了哪个
    char str[N];
    
    void insert(char *str)
    {
        int p = 0;
        for (int i = 0; str[i]; i++)
        {
            int u = str[i] - 'a';
            if (!son[p][u]) son[p][u] = ++idx;
            p = son[p][u];
        }
        // 此时的p就是str中最后一个字符对应的trie树的位置idx。
        cnt[p]++;
    }
    
    int search(char *str)
    {
        int p = 0;
        for (int i = 0; str[i]; i++)
        {
            int u = str[i] - 'a';
            if (!son[p][u]) return 0;
            p = son[p][u];
        }
        return cnt[p];
    }
    bool startsWith(char *str)
    {
        int p=0;//从根结点开始找
        for (int i = 0; str[i]; i++)
        {
            int u = str[i] - 'a';
            if (!son[p][u]) return 0;//以p为头结点的u结尾字母不存在,返回0
            p = son[p][u];//为查询下个字母做准备,往下走
        }
        return true;//找到了
    }
    int main()
    {
        int n;
        scanf("%d", &n);
        char op[2];
        while (n--)
        {
            scanf("%s%s", op, str);
            if (op[0] == 'I') insert(str);
            else if(op[0]=='F') printf("%d
    ",startsWith(str));
            else printf("%d
    ", search(str));
        }
        return 0;
    }

      

    前缀统计

    把这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;
    }
    

      

    加油啦!加油鸭,冲鸭!!!
  • 相关阅读:
    leetcode 309. Best Time to Buy and Sell Stock with Cooldown
    leetcode 714. Best Time to Buy and Sell Stock with Transaction Fee
    leetcode 32. Longest Valid Parentheses
    leetcode 224. Basic Calculator
    leetcode 540. Single Element in a Sorted Array
    leetcode 109. Convert Sorted List to Binary Search Tree
    leetcode 3. Longest Substring Without Repeating Characters
    leetcode 84. Largest Rectangle in Histogram
    leetcode 338. Counting Bits
    git教程之回到过去,版本对比
  • 原文地址:https://www.cnblogs.com/clarencezzh/p/10776849.html
Copyright © 2011-2022 走看看