Trie树(字典树)

作用

看以下几个题:

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

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

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

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

judge:n<=200000,TLE!

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

概念

单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。

优点
利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。
Trie的数据结构定义:

以小写字母字典树为例

#define MAX 26 //根据需要变化};
typedef struct Trie   {
      Trie *next[MAX];
      int count;

next 表示每层有多少种类的数,也就是子树的个数。例子是小写字母,为26。
count 表示一个字典树到此有多少相同前缀的数目。

操作

生成字典树:

思路:
从左到右扫这个单词,如果字母在相应根节点下没有出现过,就插入这个字母;否则沿着字典树往下走,看单词的下一个字母。
代码:

void createTrie(char *str)
{
    int len = strlen(str);
    Trie *p = root, *q;
    for(int i=0; i<len; ++i)
    {
        int id = str[i]-'0';
        if(p->next[id] == NULL)
        {
            q = (Trie *)malloc(sizeof(Trie));
            q->v = 1;    //初始v==1
            for(int j=0; j<MAX; ++j)
                q->next[j] = NULL;
            p->next[id] = q;
            p = p->next[id];
        }
        else
        {
            p->next[id]->v++;
            p = p->next[id];
        }
    }
    p->v = -1;   //若为结尾,则将v改成-1表示
}

利用树查找:

以查找一个前缀是否出现过为例
思路
从左往右以此扫描每个字母,顺着字典树往下找,能找到这个字母,往下走,否则结束查找,即没有这个前缀;前缀扫完了,表示有这个前缀。

代码

int search(char *str)
{
   int len = strlen(str);
    Trie *p = root;
    for(int i=0; i<len; ++i)
    {
        int id = str[i]-'0';
        p = p->next[id];
        if(p == NULL)   //若为空集,表示不存以此为前缀的串
            return 0;
        if(p->v == -1)   //字符集中已有串是此串的前缀
            return -1;
    }
    return -1;   //此串是字符集中某串的前缀
}

实战

字典树
模板题

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct ooo
{
    int next[26];   
    bool mark;  //标记
}dd[505555];
int top,n,m;
char a[20],b[20];
int creat()  //分配新的节点
{
    memset(dd[top].next,-1,sizeof(dd[top].next));  //表示指向空
    dd[top].mark=false;//表示无对应字符串
    return top++;
}
int xiab(char c)
{
    return c-'a'; //对应下标
}
void insert(int root,char *s)   //将S插入到字典树中
{ 
    int i,len=strlen(s);
    for(i=0;i<len;i++)
    {
        if(dd[root].next[xiab(s[i])]==-1)
        dd[root].next[xiab(s[i])]=creat();
        root=dd[root].next[xiab(s[i])];
    }
    dd[root].mark=true;
}
 
bool search(int root,char *s)  //询问是否出现在字典树中
{
    for(int i=0;s[i]!='\0';i++)
    {
        if(dd[root].next[xiab(s[i])]==-1)
        return false;
        root=dd[root].next[xiab(s[i])];
    }
    return dd[root].mark;
}
int main()
{
    int i,j,root;
 
    while(scanf("%d %d",&n,&m)&&(n||m))
    {
        top=0;
        root=creat();
        for(i=0;i<n;i++)
        {
        scanf("%s",a);
        insert(root,a);
        }
        for(i=0;i<m;i++)
        {
           scanf("%s",b);
           printf("%s\n",search(root,b)?"Yes":"No");
        }
    }
    return 0;
}

迷之好奇

思路:
题目要求后缀,反转成前缀即可。
但是...数据量太大,字典树会超时,题目就变成了思维题。

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N=1e5+10;
int col[N];
int a[N][15];
int flag[N];
int k;
string s1,s2;
void Insert()
{
    int p=0,i;
    for(i=0;i<s1.size();i++)
    {
        if(!a[p][s1[i]-'0']) a[p][s1[i]-'0']=++k;
        col[p]++;//记录几个单词经过了他。
        p=a[p][s1[i]-'0'];
    }
    flag[p]=1;//表示这是一个单词的末尾。
}
int search1()
{
    int p=0,i;
    for(i=0;i<s2.size();i++)
    {
        if(!a[p][s2[i]-'0']) return 0;
        p=a[p][s2[i]-'0'];
    }
    return col[p];
}
int main()
{
    int n,m,i;
    ios::sync_with_stdio(false);
    while(cin>>n)
    {
        for(i=1;i<=n;i++)
        {
          cin>>s1;
          reverse(s1.begin(),s1.end());
          Insert();
        }
         cin>>m;
        for(i=1;i<=m;i++)
        {
            cin>>s2;
            reverse(s2.begin(),s2.end());
            int num=search1();//找到单词段然后返回值。
            printf("%d\n",num);
        }
        memset(flag,0,sizeof(flag));
        memset(a,0,sizeof(a));
        memset(col,0,sizeof(col));
        k=0;//初始化。
    }
    return 0;
}

原文

上一篇:API时代,每个人都能拥有阿凡达,天了撸!


下一篇:screen-camera calibration using a thread