字典树入门

字典树

∙ \bullet 什么是字典树(百度百科):又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

字典树的建立、查询、删除

建立,插入字符

已插入一个字符串为例,首先初始化一个二维数组 t r e e [ m a n x ] [ 30 ] tree[manx][30] tree[manx][30] t r e e [ i ] [ j ] = 1 tree[i][j]=1 tree[i][j]=1 j j j 是字符串里的某个字符的ASCII码减去字符’a’的值)就代表第 i i i 号结点有一条代表字符 j + ′ a ′ j+'a' j+a的边。
插入某个字符,如果 t r e e [ i ] [ j ] = 0 tree[i][j]=0 tree[i][j]=0则建立一新的结点 r o o t root root作为当前第 i i i 号结点的子节点,并令 t r e e [ i ] [ j ] = r o o t tree[i][j]=root tree[i][j]=root,如果 t r e e [ i ] [ j ] ! = 0 tree[i][j]!=0 tree[i][j]=0,就直接顺着走第 i i i 号节点的子节点,重复上述步骤,直至遍历完整个字符串。

有时还需再用一个 s u m [ ] sum[] sum[]数组来存具有相同前缀的字符串有多少个。

int cnt=0;
void insert_word(char *str)
{
    int len = strlen(str);
    int root = 0;
    for(int i = 0 ; i < len ; i++){
        int id = str[i] - 'a';
        if(tree[root][id] == 0){
            tree[root][id] = ++cnt;
        }
        root = tree[root][id];
        sum[root]++;
    }
    return ;
}
查询字符串

原理和插入差不多,但是如果在某个结点处 t r e e [ i ] [ j ] = = 0 tree[i][j]==0 tree[i][j]==0那就肯定没有这个字符串了。 s u m [ r o o t ] sum[root] sum[root]既能知道有没有这个字符串,它存储的值又是以这个字符串为前缀的所有字符串的数量。

int query(char *str)
{
    int len = strlen(str);
    int root = 0;
    for(int i = 0;i < len ;i++){
        int id = str[i] - 'a';
        if(tree[root][id] != 0){
            root = tree[root][id];
        }
        else return 0;
    }
    return sum[root];
}
删除字符串

原理和上面两个一样,从根节点就是顺着走下去,每走到一个新的结点就 s u m [ r o o t ] − − sum[root]-- sum[root]

void delet(int n)
{
    int root = 0;
    for(int i = 30;i >= 0 ; i--)
    {
        int id = (n >> i) & 1;
        root = tree [root] [id];
        sum[root]--;
    }
}

∙ \bullet 例题:

大多数考试中单选题目的选项都是ABCD四项。小Hi为了更高效地传递选项信息,将ABCD按如下方式编码:
A: 101, B:11, C:0, D:100
例如对于ABCD,编码后的01串是101110100。
现在给定一个编码后的01串,请你译出原本的选项串是什么。
Input
一个01串,长度不超过100000。
输入保证有唯一解。
Output
一个只包含ABCD的字符串代表答案。
Sample Input

    101110100

Sample Output

    ABCD
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#include <set>
#include <iostream>
#include <queue>
#include <string>
#include <math.h>
typedef long long LL;
using namespace std;
const int maxn=3e5+10;
int tree[maxn][3],sum[maxn];
char val[maxn];
char str[maxn];
int cnt = 0;
void insert_word(char *str,char s)
{
    int len = strlen(str);
    int root = 0;
    for(int i = 0 ; i < len ; i++)
    {
        int id = str[i] - '0';
        if(tree[root][id] == 0)
            tree[root][id] = ++cnt;
        root = tree[root][id];
        sum[root]++;
    }
    val[root] = s;//记录root结点代表的字母
}

void query(char *str)
{
    int len = strlen(str);
    int root = 0;
    for(int i = 0;i < len ; i++)
    {
        int id = str[i] - '0';
        if(tree[root][id] != 0)
            root = tree[root][id];
        else
            root=tree[root][!id];
        if(val[root] == 'A' || val[root] == 'B' || val[root] == 'C' || val[root] == 'D')
        {
            printf("%c",val[root]);
            root = 0;//如果找到了,就从根结点开始找下一个字母
        }
    }
    printf("\n");
}

void init()
{
    cnt = 0;
    memset(tree,0,sizeof(tree));
    memset(val,0,sizeof(val));
    insert_word("101",'A');
    insert_word("11",'B');
    insert_word("0",'C');
    insert_word("100",'D');
}


int main()
{
    scanf("%s",str);
    init();
    query(str);
    return 0;
}
补充

除了字符串问题,还可以解决这类问题:给你n个数,再给你x,让你从n个数里找出一个数m,使得m^x(m异或x,同0异1)最大或者最小。
直接从最高位开始,讲n个数的二进制每一位插入到字典树中,如果要使m^x最大,就每次走和 x 对应的二进制相反的边就行了。反之,就走和 x 对应的二进制相同的边就行了。

∙ \bullet 例题:
Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。你能证明人类的智慧么?
Input
输入包含若干组测试数据,每组测试数据包含若干行。
输入的第一行是一个整数T(T < 10),表示共有T组数据。
每组数据的第一行输入两个正整数N,M(<1=N,M<=100000),接下来一行,包含N个正整数,代表 Zeus 的获得的集合,之后M行,每行一个正整数S,代表 Prometheus 询问的正整数。所有正整数均不超过2^32。
Output
对于每组数据,首先需要输出单独一行”Case #?:”,其中问号处应填入当前的数据组数,组数从1开始计算。
对于每个询问,输出一个正整数K,使得K与S异或值最大。
Sample Input

    2
    3 2
    3 4 5
    1
    5
    4 1
    4 6 5 6
    3

Sample Output

    Case #1:
    4
    3
    Case #2:
    4
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#include <set>
#include <iostream>
#include <queue>
#include <string>
typedef long long LL;
using namespace std;
const int maxn = 1e5+10;
const int inf = 999999999;
LL tree[32*maxn][3],sum[32*maxn],cnt=0;
int n,m;

void insert_word(LL num){
    int root = 0;
    for(int i = 32 ; i >= 0 ; i--){
        int id = (num >> i) & 1;
        if(tree[root][id] == 0){
            tree[root][id] = ++cnt;
        }
        root = tree[root][id];
    }
    sum[root] = num;//记录root结点代表的值
}

LL query(LL num)
{
    int root = 0;
    for(int i = 32 ; i >= 0 ; i--){
        int id = (num >> i) & 1;
        if(tree[root][!id] != 0){
            root = tree[root][!id];
        }
        else {
            root = tree[root][id];
        }
    }
    return sum[root];
}

void init()
{
    memset(tree,0,sizeof(tree));
    memset(sum,0,sizeof(sum));
}

void in()
{
    for(int i = 1 ; i <= n ; i++){
        LL x;
        scanf("%lld",&x);
        insert_word(x);
    }
}

int main()
{
        int t;
        scanf("%d",&t);
        int flag=1;
        while(t--){
            scanf("%d%d",&n,&m);
            init();
            in();
            printf("Case #%d:\n",flag++);
            while(m--){
                LL y;
                scanf("%lld",&y);
                printf("%lld\n",query(y));
            }
        }
        return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值