字典树基本知识点(sdut)

本文深入讲解字典树(Trie树)的基本概念、性质及应用,包括插入、查找和删除等基本操作,以及如何实现这些操作的具体代码示例。字典树在文本检索、排序和保存大量字符串中发挥重要作用。

学习目标
了解字典树的基本思想,掌握一种或多种字典树的编写方法。
为了让大家更好的了解,字典树的基本思想,我在这里用画图的方法来带大家了解一下字典树的插入,查找,删除操作。

字典树的简单介绍

又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,它的核心思想是空间换时间,空间消耗大但是插入和查询有着很优秀的时间复杂度。

先回顾一下树形结构:
树形结构-- 一对多的关系

数据结构中,使用树形结构表示数据表素之间一对多的关系,树形结构是一种非线型结构.
定义:
树(Tree)是n(n≥0)个相同数据类型的数据元素的集合.树中的数据元素称为节点(Node).。n=0的树称为空树(Empty Tree);对于n>0的任意非空树T有:
(1)有且仅有一个特殊的结点称为树的根(Root)结点,根没有前驱结点;
(2)若n>1,则除根结点外,其余结点被分成了m(m>0)个互不相交的集合T1,T2,…,Tm,其中每一个集合Ti(1≤i≤m)本身又是一棵树。树T1,T2,…,Tm称为这棵树的子树(Subtree)。
由树的定义可知,树的定义是递归的,用树来定义树。因此,树(以及二叉树)的许多算法都使用了递归。
树的形式定义为:树(Tree)简记为T,是一个二元组,
T = (D, R)
其中:D是结点的有限集合;
R是结点之间关系的有限集合。

树具有下面两个特点

(1)树的根结点没有前驱结点,除根结点之外的所有结点有且只有一个前驱结点。
(2)树中的所有结点都可以有零个或多个后继结点。
实际上,第(1)个特点表示的就是树形结构的“一对多关系”中的“一”,第(2)特点表示的是“多”。
在这里插入图片描述
树的相关术语:

1、结点(Node):表示树中的数据元素,由数据项和数据元素之间的关系组成。在图中,共有10个结点。
2、结点的度(Degree of Node):结点所拥有的子树的个数,在图中,结点A的度为3。
3、树的度(Degree of Tree):树中各结点度的最大值。在图5.1中,树的度为3。
4、叶子结点(Leaf Node):度为0的结点,也叫终端结点。在图5.1中,结点E、F、G、H、I、J都是叶子结点。
5、分支结点(Branch Node):度不为0的结点,也叫非终端结点或内部结点。在图5.1中,结点A、B、C、D是分支结点。
6、孩子(Child):结点子树的根。在图中,结点B、C、D是结点A的孩子。
7、双亲(Parent):结点的上层结点叫该结点的双亲。在图中,结点B、C、D的双亲是结点A。
8、祖先(Ancestor):从根到该结点所经分支上的所有结点。在图中,结点E的祖先是A和B。
9、子孙(Descendant):以某结点为根的子树中的任一结点。在图中,除A之外的所有结点都是A的子孙。
10、兄弟(Brother):同一双亲的孩子。在图5.1中,结点B、C、D互为兄弟。
11、结点的层次(Level of Node):从根结点到树中某结点所经路径上的分支数称为该结点的层次。根结点的层次规定为1,其余结点的层次等于其双亲结点的层次加1。
12、堂兄弟(Sibling):同一层的双亲不同的结点。在图中,G和H互为堂兄弟。
13、树的深度(Depth of Tree):树中结点的最大层次数。在图5.1中,树的深度为3。
14、无序树(Unordered Tree):树中任意一个结点的各孩子结点之间的次序构成无关紧要的树。通常树指无序树。
15、有序树(Ordered Tree):树中任意一个结点的各孩子结点有严格排列次序的树。二叉树是有序树,因为二叉树中每个孩子结点都确切定义为是该结点的左孩子结点还是右孩子结点。
16、森林(Forest):m(m≥0)棵树的集合。自然界中的树和森林的概念差别很大,但在数据结构中树和森林的概念差别很小。从定义可知,一棵树有根结点和m个子树构成,若把树的根结点删除,则树变成了包含m棵树的森林。当然,根据定义,一棵树也可以称为森林。

树的逻辑表示
字典树它有3个基本性质

1,根结点不包含任何字符信息;
2,如果字符的种数为n,则每个结点的出度为n(这样必然会导致浪费很多空间,这也是trie的缺点);
3,查找,插入复杂度为O(n),n为字符串长度。

基本操作

其基本操作有:查找、插入和删除,当然删除操作比较少见。

搜索字典项目的方法为:

(1) 从根结点开始一次搜索; (2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索; (3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。 (4) 迭代过程…… (5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。 其他操作类似处理
字典树的应用:

1.串的快速检索
给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。 在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。
2.“串”排序
给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出 用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。
3.最长公共前缀
对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为当时公共祖先问题。

这里补充一下,其实对于查找单词和统计前缀出现次数时也可以用map函数,但当要统计的单词数目较大时就会TLE。但map函数还是很实用的一种函数,建议大家私下去学习了解一下,这里我就不细讲了。

(一)Trie的定义

Trie树的键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀(prefix),从根节点到当前结点的路径上的所有字母组成当前位置的字符串,结点可以保存当前字符串、出现次数、指针数组(指向子树)以及是否是结尾标志等等。

struct node
{
    int num;//前缀出现的次数
    struct node *next[SIZE];//孩子节点的数目
    bool flag; //判断到这个位置是否是一个单词
    node()//构造函数
    {
        num=0;
        memset(next,NULL,sizeof(next));
        flag=0;
    }
};

(二)Trie树的基本操作
(1)插入操作 O(n)
构建Trie树的基本算法
逐一把每则单词的每个字母插入Trie。插入前判断前缀是否存在,如果存在,共享节点,否则创建对应的节点和边。

void Creat(char *s)
{
    node *p=root;
    int i,id,len=strlen(s);
    for(i=0; i<len; i++)
    {
        id=s[i]-'a';
        if(p->next[id]==NULL)   //不存在该子节点,新建子节点
        {
            p->next[id]=new node;
        }
        p=p->next[id];
        p->num++;    //统计前缀数目
    }
    p->flag=1;   //单词结尾标记
}

在这里插入图片描述
(2)查询操作 O(n)
和插入操作相仿,若查询途中某一个结点并不存在,则直接就return返回。否则继续下去,当字符串结束时,节点的单词结尾标志为一,那么证明此字符串存在。

void Find(char *s)
{
    node *p=root;
    int i,id,len=strlen(s);
    for(i=0; i<len; i++)
    {
        id=s[i]-'a';
        if(p->next[id]==NULL)   //节点不存在
        {
            printf("No\n");
            return;
        }
        p=p->next[id];
    }
    if(p->flag==1)   //判断是否为结尾
        printf("Yes\n");
    else printf("No\n");
    return;
}

(3)删除操作

数据可能会比较大,需要查询完之后释放内存,递归删除整个树

void Delete(node *root)
{
    if(root==NULL)
        return;
    for(int i=0; i<26; i++)
    {
        if(root->next[i])  //如果子节点不为空,继续删除子节点
            Delete(root->next[i]);
    }
    delete(root);
}

这里简单介绍一下delete函数,大家了解一下

delete()在c++中经常用到,但是其实它本身和new对应,并不是函数,而是关键词
在这里,和 sizeof 类似,new 和 delete 也不是函数,它们都是 C++ 定义的关键字,通过特定的语法可以组成表达式。和 sizeof 不同的是,sizeof 在编译时候就可以确定其返回值,new 和 delete 背后的机制则比较复杂。
delete 就做了两件事情:
调用 指向对象的析构函数,对打开的文件进行关闭。
通过标准库函数 operator delete 来释放该对象的内存,传入函数的参数为 对象 的值。
与new/delete不同,malloc()/free()才是标准库函数

据学长说oj平台在调用new/delete函数时,可能会出现bug

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值