算法-Trie树 -字典树

一、编程题

题目描述

牛牛想尝试一些新的料理,每个料理需要一些不同的材料,问完成所有的料理需要准备多少种不同的材料。

输入描述:

每个输入包含 1 个测试用例。每个测试用例的第 i 行,表示完成第 i 件料理需要哪些材料,各个材料用空格隔开,输入只包含大写英文字母和空格,输入文件不超过 50 行,每一行不超过 50 个字符。

输出描述:

输出一行一个数字表示完成所有料理需要多少种不同的材料。

根据二中的思想 用字典序trie树方式 其中2100是一个安全上限 根据牛客网改的

如果这个上限超出内存限制 需要参考方法2变成动态分配

思路: 维护一个字典树序列 a[i][j]=k 编号i的第j个孩子的编号是K 编号是输入单词序列与字典树结合的顺序。

        插入和查找同时进行时,考虑la和l都是新的,即后到的前缀作为单词也应计算 因此需要一个bool标记是否访问过

#include<iostream>
#include<cstdlib>
#include<string.h>
using namespace std;
char s[55];
int total = 0;
int cur = 0; //编号
struct tr{
    int id;
    bool endflag;
    tr():id(0),endflag(false){}
};
tr trie[2500][26];
void count(int rt, char * s, int len)
{
    for(int i=0;i<len;i++)
    {
        int gap = s[i]-'A';
        if(!trie[rt][gap].id)//插入
        {
            trie[rt][gap].id = ++cur;
            if(i == len-1) //结尾证明是新单词
            {
                total++;
                trie[rt][gap].endflag = true;
                //cout<<len-1<<' '<<total<<' '<<s<<endl;
            }
        }
        if(i == len-1 && !trie[rt][gap].endflag) //结尾证明是新单词
        {
            total++;
            trie[rt][gap].endflag = true;
               // cout<<len-1<<' '<<total<<' '<<s<<endl;
        }
        rt = trie[rt][gap].id; //下一次的起始节点
    }
}

int main()
{
    int rt = 0;
    //freopen("input.txt","r",stdin);
    while(cin>>s)
    {
        count(rt,s,strlen(s));
        //cout<<s<<endl;
    }
    //freopen("CON","r",stdin);
    cout<<total<<endl;
    //system("pause");
    return 0;
}

方法2:

#include<iostream>
#include<cstring>
using namespace std;
int count = 0;//单词数统计,用于分配id,每个单词一个id
/*字典树节点类:
flag:词尾标志
id:节点hash值
next:后续字母节点连接地址
*/
class Trie_node{
public:
    bool flag;
    int id;
    Trie_node *next[27];
    Trie_node(){
        flag = false;
        id = 0;
        memset(next, 0, sizeof(next));
    }
}root;
/*hash函数:
返回字符串的hash值
*/
int Hash(char *s){
    Trie_node *p = &root;
    int len = 0;
    while(s[len] != '\0'){
        int index = s[len++] - 'A';
        if(!p->next[index])
            p->next[index] = new Trie_node;
        p = p->next[index];
    }
    if(!p->flag){
        p->id = count++;
        p->flag = true;
    }
    return p->id;
}
 
int main(){
    char step[51];
    while(cin>>step){
        Hash(step);
    }
    cout<<count<<endl;
    return 0;
}



二、参考Trie树简介:

https://www.cnblogs.com/TheRoadToTheGold/p/6290732.html

1、单词是否出现

/*查询单词是否整体出现
  trie tree的储存方式:将字母储存在边上,边的节点连接与它相连的字母
  trie[rt][x]=tot:rt是上个节点编号,x是字母,tot是下个节点编号
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 2000010
using namespace std;
int tot=1,n;
int trie[maxn][26];
//bool isw[maxn];查询整个单词用
void insert(char *s,int rt)
{
    for(int i=0;s[i];i++)
    {
        int x=s[i]-'a';
        if(trie[rt][x]==0)//现在插入的字母在之前同一节点处未出现过
        {
            trie[rt][x]=tot++;//字母插入一个新的位置,否则不做处理
        }
        rt=trie[rt][x];//为下个字母的插入做准备
    }
    /*isw[rt]=true;标志该单词末位字母的尾结点,在查询整个单词时用到*/
}
bool find(char *s,int rt)
{
    for(int i=0;s[i];i++)
    {
        int x=s[i]-'a';
        if(trie[rt][x]==0)return false;//以rt为头结点的x字母不存在,返回0
        rt=trie[rt][x];//为查询下个字母做准备
    }
    return true;
    //查询整个单词时,应该return isw[rt]
}
char s[22];
int main()
{
    tot=0;
    int rt=1;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        insert(s,rt);
    }
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        if(find(s,rt))printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

2、前缀出现次数

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int trie[400001][26],len,root,tot,sum[400001];
bool p;
int n,m; 
char s[11];
void insert()
{
    len=strlen(s);
    root=0;
    for(int i=0;i<len;i++)
    {
        int id=s[i]-'a';
        if(!trie[root][id]) trie[root][id]=++tot;
        sum[trie[root][id]]++;//前缀后移一个位置保存 
        root=trie[root][id];
    }
}
int search()
{
    root=0;
    len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int id=s[i]-'a';
        if(!trie[root][id]) return 0;
        root=trie[root][id];
    }//root经过此循环后变成前缀最后一个字母所在位置的后一个位置 
    return sum[root];//因为前缀后移了一个保存,所以此时的sum[root]就是要求的前缀出现的次数 
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        insert();
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        cin>s;
        printf("%d\n",search());
    }
}

猜你喜欢

转载自blog.csdn.net/yanbao4070/article/details/80913475