• 字典树基础进阶全掌握(Trie树、01字典树、后缀自动机、AC自动机)


    字典树

    概述

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

    光说不懂,上引例——

    NKOJ 1934 外地人

        你考入大城市沙坪坝的学校, 但是沙坪坝的当地人说着一种很难懂的方言, 你完全
    听不懂。 幸好你手中有本字典可以帮你。 现在你有若干个听不懂的方言需要查询字典。
    输入格式
    第一行,两个整数n和m。
    接下来有n行表示字典的内容,每行表示一条字典的记录。每条记录包含两个空格间隔的单词,第一个单词为英文单词,第二个单词为对应的沙坪坝方言。
    接下来有m行,每行一个单词,表示你要查询的沙坪坝方言。
    输出格式
    输出m行,每行一个英文单词,表示翻译后的结果。
    如果某个单词字典查不到,输出"eh"
    样例输入
    5  3
    dog  ogday
    cat  atcay
    pig  igpay
    froot  ootfray
    loops  oopslay
    atcay
    ittenkay
    oopslay
    样例输出
    cat
    eh
    loops
     注:所有单词都用小写字母表示, 且长度不超过10。
    传送门http://oi.nks.edu.cn/zh/Problem/Details/1934

        我们看一下这张图先感受一下Trie树的结构,它是首先建立一个Root根节点,然后在读取后来的字符串的同时,从根节点出发,查找字符串每一位的节点是否存在。若存在,就从这一位出发继续查找下一位;若不存在,就建立这个节点。反复以上过程。注意,Trie树是将字符转换为ASCLL码存取,注意转换。

        显然,借用这样的数据结构,我们可以方便存取大量字符串,大幅度优化空间复杂度。

    (不知道ASCLL的点这里)

    Trie Tree的特点

      1. 根节点不包含字符, 除根节点外每一个节点都只包含一个字符。

      2. 从根节点到某一节点, 路径上经过的字符连接起来, 为该节点对应的字符串。

      3. 在trie树中查找一个关键字的时间和树中包含的结点数无关, 而取决于组成关键字的字符数。 也就是查找字符串s的时间为O(s.length())

      4. 如果要查找的关键字可以分解成字符序列且不是很长, 利用Trie树查找速度优于二叉查找树。
      如:若关键字长度最大是5, 则利用Trie树, 利用5次比较可以从265=11881376个可能的关键字中检索出指定的关键字。 而利用二叉查找树至少要进行log2265=23.5次比较。

    接下来先给出引例题解的main函数部分(部分初始化未给出)——
    struct node {
    	int Num; //如果该节点是一个单词的结尾,记录对应单词的编号
    	int Next[26]; //儿子节点的编号
    }trie[1000001];
    string s[100001], a;
    int main() {
    	cin >> n >> m;
    	for (k = 1; k <= n; k ++){ 
    		cin >> s[k] >> a; 
    		Insert(a, k); 
    	}
    	for (k = 1; k <= m; k ++) {
    		cin >> a;
    		ans = Find(a);
    		if (ans)cout << s[ans];
    		else cout << "eh" << endl;
    	}
    	return 0;
    }
    
    接着是两个函数的部分——
    void Insert(string c, int k) {
    	int i, t, len, p = 1;
    	len = c.length();
    	for (i = 0; i < len; i ++) {
    		t = c[i] - 'a';//将字符c[i]转换成值为0到25的数字,比如'a'转换为0,'b'转换为1,‘c’转换为2……
    		if (trie[p].Next[t] == 0) { //若p没有值为t的儿子
    			tot ++; //新增一个编号为tot的节点
    			trie[p].Next[t] = tot; //记下p的值为t的孩子节点的编号
    			p = trie[p].Next[t]; //p指向新添加的节点
    			trie[p].Num = 0; //初始化新添加的节点,将其标记为不是单词的结尾
    		} else p = trie[p].Next[t]; //若p存在值为t的儿子,p指向该儿子,继续讨论
    	} 
    	trie[p].Num = k; //for循环已执行完,说明第k个单词已加入,在单词结尾做上标记
    }  
    

    int Find(string c) {
    	int i, t, len, p = 1;
    	len = c.length();
    	for (i = 0; i < len; i ++) {
    		t = c[i] - 'a';
    		if (trie[p].Next[t] == 0)return 0; //当前要匹配值为t的字母,若没有则结束
    		p = trie[p].Next[t]; //若存在值为t的字母,则继续匹配
    	} 
    	return trie[p].Num; //若for循环执行完毕,说明找到了需要的单词,返回其编号
    }
    
    以上的代码几乎就是字典树的模板,在不同的题中main函数或许有所不同,可以借此熟悉一下字典树的工作原理,再酌情修改。

    Trie树的应用

     (1) 字符串检索
     (2) 字符串最长公共前缀

    #######提供几道字典树的简单练习:
    NKOJ 1931 电话簿
    NKOJ 1932 找出克隆人
    NKOJ 1933 彩色木条
    NKOJ 1935 图书管理员

    01字典树

        01字典树和普通的字典树原理类似,只不过把插入字符改成了插入二进制串的每一位(0或1)。裸的Trie树可以降低空间复杂度,而01还可以降低时间复杂度。

        它与普通的字典树一样先建立Root根节点,但它不存取复杂字符串,而只能存取含有“0”或“1”字符串或数字串。(所以十进制整数可以看做二进制进行存取)以首位为第一个节点建树,按照前面讲解的普通Trie树的工作原理,我们可以得到一个二叉树,而深度由数字范围决定,比如深度为20的01字典树可以进行存取0~221-1的所有数。

    后缀自动机

    AC自动机

    (后续补充)

  • 相关阅读:
    那些值得收藏的神奇的网站,使用RSS阅读器订阅喜欢的网站 --授人以鱼不如授人以渔
    截图与屏幕录像利器:FastStone Capture
    Java基础数据类型
    刚刚开通了博客园,欢迎来踩
    ETL 各种小问题笔记
    SpringBoot 项目打包部署Resin遇到的问题
    跟据html页面生成图片方便打印分享
    微信将用户信息转为一张图片(将html转为图片)
    Js处理本地视频和第三方视频播放的问题
    反射+特性实现 类和XML文档的序列化反序列化
  • 原文地址:https://www.cnblogs.com/Limbo-To-Heaven/p/11352589.html
Copyright © 2020-2023  润新知