Trie树(字典树)

目录

1.Trie字符串统计

1.1基本介绍

1.2主要性质

1.3 典例

1.3.1实现思路

1.3.2模板

1.3.2.1insert函数

1.3.2.2query函数

1.3.3完整代码

2.变式:大按钮 

2.1题目

 2.2思路

 2.3代码


1.Trie字符串统计

1.1基本介绍

Trie 树,又称字典树,是用来 高效存储和查找字符串集合 的一种数据结构查找时,可以 高效的查找某个字符串 是否 在Trie 树中出现过,并且可以查找出现了多少次
利用字符串的 公共前缀 来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

1.2主要性质

1.根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
2.从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
3.每个节点的所有子节点包含的字符互不相同。

图例: 

1.3 典例

维护一个字符串集合,支持两种操作:

  1. I x 向集合中插入一个字符串 xx;
  2. Q x 询问一个字符串在集合中出现了多少次。

共有 N 个操作,所有输入的字符串总长度不超过 10^5,字符串仅包含小写英文字母。

输入格式

第一行包含整数 N,表示操作数。

接下来 N 行,每行包含一个操作指令,指令为 I x 或 Q x 中的一种。

输出格式

对于每个询问指令 Q x,都要输出一个整数作为结果,表示 xx 在集合中出现的次数。

每个结果占一行。

1.3.1实现思路
1.变量设置:
(1)设置一个二维数组 son[p][u] 初始为 0 p 代表当前结点, u 代表当前结点的某个子结点( u 的取值为 0~25 对应 26 个字母),即 son[p][u] 不为 0 代表 p 结点下连接着下标为 u的字母。
(2)设置一个idx 指向要操作的数组下标位置(这个idx是具体什么呢,是新需要插入的节点的编号,我们知道trie高效的原因之一是储存了公共的前缀,所以这些前缀节点再次插入的时候,是不需要另外储存的,idx只是需要新储存的节点编号)。
(3) 设置一个数组 cnt[] 标记以该字符串出 现次数, cnt[p] 表示以 p 结尾的字符串出现次数。


 2.插入操作:每次插入一个字符串,循环处 理每一个字符。利用 u=str[i]-'a' 将每个字符转化为整数操作,判断 son[p][u] 的值是否为0。若为0,不存在该字符,就插入该字符,

即令 son[p][u]=++idx ,然后下移到子结点继续插入字符串的下一个字符;若不为 0,就表示之前已经插入过该字符,直接下移。最后该字符串插入结束。最后p就是该字符串结尾的坐标。

3.查询操作:类似插入操作进行判断,若出现某一次 s[p][u] 0就此结束,意味着不存在该字符串,直到循环 结束,存在该字符串,返回计数数组cnt[p]。

1.3.2模板
1.3.2.1insert函数
  private static void insert(String str) {
        int p = 0;//类似指针,指向当前节点
        for (int i = 0; i < str.length(); i++) {
            int u = str.charAt(i) - 'a';//将字母转化为数字
            if (son[p][u] == 0) {
                //该节点不存在,创建节点,其值为下一个节点位置
                son[p][u] = ++idx;
            }
            p = son[p][u]; //使“p指针”指向下一个节点位置
        }
        cnt[p]++;//结束时的标记,也是记录以此节点结束的字符串个数
    }
1.3.2.2query函数
    private static int query(String str) {
        int p = 0;
        for (int i = 0; i < str.length(); i++) {
            int u = str.charAt(i) - 'a';
            if (son[p][u] == 0) {
                //该节点不存在,即该字符串不存在
                return 0;
            }
            p = son[p][u];
        }
        return cnt[p];
    }
1.3.3完整代码

import java.util.Scanner;

public class Main {
    /*维护一个字符串集合,支持两种操作:
       I x 向集合中插入一个字符串 x;
       Q x 询问一个字符串在集合中出现了多少次。
       共有 N个操作,所有输入的字符串总长度不超过 1e5,字符串仅包含小写英文字母。*/
    /*输入格式
    第一行包含整数 N,表示操作数。
    接下来 N行,每行包含一个操作指令,指令为 I x 或 Q x 中的一种。*/
    static int N = 100010, idx;
    static int[][] son = new int[N][26];
    static int[] cnt = new int[N];

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int q = in.nextInt();
        in.nextLine();
        while (q-- > 0) {
            String[] s = in.nextLine().split(" ");
            if (s[0].equals("I")) {
                insert(s[1]);
            } else {
                System.out.println(query(s[1]));
            }
        }
    }

    private static int query(String str) {
        int p = 0;
        for (int i = 0; i < str.length(); i++) {
            int u = str.charAt(i) - 'a';
            if (son[p][u] == 0) {
                //该节点不存在,即该字符串不存在
                return 0;
            }
            p = son[p][u];
        }
        return cnt[p];
    }

    private static void insert(String str) {
        int p = 0;//类似指针,指向当前节点
        for (int i = 0; i < str.length(); i++) {
            int u = str.charAt(i) - 'a';//将字母转化为数字
            if (son[p][u] == 0) {
                //该节点不存在,创建节点,其值为下一个节点位置
                son[p][u] = ++idx;
            }
            p = son[p][u]; //使“p指针”指向下一个节点位置
        }
        cnt[p]++;//结束时的标记,也是记录以此节点结束的字符串个数
    }
}

2.变式:大按钮 

来源:Google Kickstart2018 Round H Problem A

2.1题目

 

 2.2思路

1.使用Trie树,把所有的禁用的字符串P,都insert到Trie树中,不需要判断前缀,只需要给加进去的P字符串的末尾加一个标记(is_end)即可。
2.d写一个dfs()函数用来计算出需要减掉多少个方案,直接对Trie树进行dfs就行。
 

 2.3代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int N = 5010, n, idx;
    static int[][] son = new int[N][2];
    static boolean[] is_end = new boolean[N];

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int T = in.nextInt();
        for (int cases = 1; cases <= T; cases++) {
            for (int i = 0; i < N; i++) {
                Arrays.fill(son[i], 0);
            }
            Arrays.fill(is_end, false);
            idx = 0;
            n = in.nextInt();
            int p = in.nextInt();
            for (int i = 0; i < p; i++) {
                String str = in.next();
                insert(str);
            }
            System.out.printf("Case #%d: %d\n", cases, (1L << n) - dfs(0, 0));
        }
    }

    private static long dfs(int p, int s) {
        if (is_end[p]) {
            return 1L << (n - s);
        }
        long res = 0;
        for (int i = 0; i < 2; i++) {
            if (son[p][i] != 0) {
                res += dfs(son[p][i], s + 1);
            }
        }
        return res;
    }

    private static void insert(String str) {
        int p = 0;
        for (int i = 0; i < str.length(); i++) {
            int u = str.charAt(i) == 'B' ? 0 : 1;
            if (son[p][u] == 0) {
                son[p][u] = ++idx;
            }
            p = son[p][u];
        }
        is_end[p] = true;
    }
}

以上为我个人的小分享,如有问题,欢迎讨论!!! 

都看到这了,不如关注一下,给个免费的赞 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值