问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Trie树的插入、查询、删除和部分应用

创作时间:
作者:
@小白创作中心

Trie树的插入、查询、删除和部分应用

引用
CSDN
1.
https://blog.csdn.net/m0_52712149/article/details/119715867

Trie树,又称字典树或前缀树,是一种用于快速检索的多叉树结构。它在处理大量字符串数据时表现出色,广泛应用于搜索引擎、自动补全系统等领域。本文将详细介绍Trie树的基本概念、实现方法及其在单词查询和词频统计等场景中的应用。

Trie树基本知识

Trie树,又称字典树、前缀树,是一种树形结构,是哈希树的变种,是一种用于快速检索的多叉树结构。

典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

它长这个样子:

结构体的构成

struct trieNode
{
    char val;///指的是当前指针直接指向的字母
    trieNode** son;///类似为指向trieNode类型的数组,其中下标1-25对应'a'-'z'
    int cnt;///表示字符结束的标志,为0则未结束,其他同时也指该单词出现的次数
    trieNode(char c)///初始化
    {
        val=c;
        son=new trieNode*[N];
        for(int i=0; i<N; i++)
        {
            son[i]=NULL;
        }
        cnt=0;
    }
};

Trie树的插入

简单思路便是遇到一个新的字符串便遍历该字符串,遇到每个合适的字符就直接插入,并且令p指向下一个对应的位置。

注:目前我的代码只包括小写字母,不包括大写字母和其他的字符,所以要将大写字母转化为小写字母,同时不能插入其他的字符。

void inserts(char s[])
{
    trieNode *p=root;
    int i;
    int c;
    for(i=0; i<strlen(s); i++)
    {
        if(s[i]>='A' && s[i]<='Z') ///将大写转化为小写
            c=s[i]-'A';
        else if(s[i]>='a' && s[i]<='z')
            c=s[i]-'a';
        else
            continue;
        if(!p->son[c])
            p->son[c]=new trieNode(c);
        p=p->son[c];
        p->val=c+'a';
    }
    p->cnt++;
}

Trie树的查询

此处查询与插入类似,返回该单词出现的次数。

int query(char s[])
{
    trieNode *p=root;
    int i;
    for(i=0; i<strlen(s); i++)
    {
        int c=s[i]-'a';
        if(!p->son[c]) return 0;
        p=p->son[c];
    }
    return p->cnt;
}

Trie树的删除

删除Trie树的一个单词貌似是有很多方法的,我这里是在单词存在的情况下让出现的次数(对应cnt)减一,如果要将全部对应该单词删掉,让cnt=0即可。

int Delete(char s[])
{
    trieNode *p=root;
    if(query(s)==0)
    {
        printf("不存在该单词!\n");
        return 0;
    }
    for(int i=0; i<strlen(s); i++)
    {
        int c=s[i]-'a';
        if(!p->son[c]) return 0;
        p=p->son[c];
    }
    p->cnt--;
    return 1;
}

Trie树的应用

因为插入文本下面经常用到,所以就把插入文本单独拿出来。

插入文本

简单思路就是对写入的文本进行分割,分割成功后便直接利用inserts()插入该单词,并且把所有出现的单词存在一个结构体容器中,方便之后的遍历

///写入文本,插入相关单词
void Text()
{
    //1-以单个字符来输入文本,以空格来分割,将前面已经出现的单词插入root中
    //以#为结束标志
    char words[50];
    int i=0,j=0;
    printf("请输入文本(输入#代表结束):\n");
    ///插入词语
    char ch='+';
    int Flags=1;
    while(1)
    {
        scanf("%c",&ch);
        if((ch>='A'&&ch<='Z')||(ch>='a'&&ch<='z'))
        {
            words[i++]=ch;
            Flags=1;
        }
        else if(Flags==1)
        {
            words[i++]='\0';
            Flags=0;
            printf("words是:%s\n",words);
            ///printf("length是:%d\n",strlen(words));
            inserts(words);
            ///printf("过了insert\n");
            i=0;
        }
        if(ch=='#') break;
    }
    ///删除CONTAINER中重复的单词
}

应用1:检索单词是否出现在文本中

主要利用上面已经写过的query()函数

void accour()
{
    root=new trieNode(' ');///建立根节点
    Text();
    //输入单个单词询问是否有出现,直接用query
    while(1)
    {
        system("cls");
        printf("请输入要查询的单词,输入0表示结束");
        char s[50];
        cin>>s;
        if(strcmp(s,"0")==0)
            break;
        if(query(s)==0)
            printf("不存在");
        else
            printf("存在,且出现了%d次\n",query(s));
        system("pause");
    }
}

应用2:统计所有出现过的单词词频

因为我本人对DFS技术还是不够好,特别是当前还需要处理字符串前缀相同的问题,所以捣鼓了好久,终于得到下面的代码。我是主要利用栈来存储已经度过的节点,利用DEVIDE数组存储经过的字符,再利用Devide的加减(当前节点不是栈头的儿子则要一直减1),从而表示字符串长度,最终成功输出

void DFS(trieNode *p)
{
    int i;
    trieNode *top=STA.top();
  ///这里的Devide可能不太准确
    while(STA.top()->son[p->val-'a']!=p && !STA.empty())
    {
        Devide--;
        STA.pop();
//        printf("不相等\n");
    }
    if(p!=root)
        DEVIDE[Devide++]=p->val;
//    printf("Devide是:%d\n",Devide);
//    if(STA.top()->son[p->val-'a']==p)
//        printf("相等\n");
    if(p->cnt!=0)
    {
        DEVIDE[Devide]='\0';
        printf("%s:",DEVIDE);
        printf("%d\n",query(DEVIDE));
    }
    STA.push(p);
//    printf("测试%c\n",p->val);
    extra=p;
    for(i=0;i<N;i++)
    {
        if(p->son[i]!=NULL && p!=NULL)
        {
            DFS(p->son[i]);
        }
    }
}
void Traves()
{
    root=new trieNode(' ');///建立根节点
    Text();
    printf("下面是所有单词以及出现的次数:\n");
//    printf("what?\n");
    extra=root;
    Devide=0;
    STA.push(root);
    DFS(root);
}

因为一开始我想写出DFS方法的代码时出现了太多错误,所以也想用过不用DFS的,大概方法是这样的:

可以创建一个结构体,存下所有的单词和出现的频率,最终直接输出就行.这个方法思路比DFS简单,但是说实话耗费的时间和空间就要大了不少,没有达到原先存储为trie树所带来的优势,所以我最后还是努力写写DFS方法,收获也不少( •̀ ω •́ )✧

typedef struct TrieWords
{
    char words[50];///存在的单词
    int cnt;///存下次数
}TrieWords;
TrieWords CONTAINER[1000];///存下所有单词的容器
///不用DFS时text需要换成这个,有些区别
void Text()
{
    container=0;
    //1-以单个字符来输入文本,以空格来分割,将前面已经出现的单词插入root中
    //以#为结束标志
    char words[50];
    int i=0,j=0;
    printf("请输入文本(输入#代表结束):\n");
    ///插入词语
    char ch='+';
    int Flags=1;
    while(1)
    {
        scanf("%c",&ch);
        if((ch>='A'&&ch<='Z')||(ch>='a'&&ch<='z'))
        {
            words[i++]=ch;
            Flags=1;
        }
        else if(Flags==1)
        {
            words[i++]='\0';
            Flags=0;
            printf("words是:%s\n",words);
            int Repos=0;
            for(j=0;j<container;j++)
            {
                if(strcmp(CONTAINER[j].words,words)==0)
                    {
                        Repos=1;
                        break;
                    }
            }
            if(Repos==0)///还没重复,cnt为0
            {
                strcpy(CONTAINER[container++].words,words);
                CONTAINER[container-1].cnt=1;
            }
            else        ///发现已经出现,在j已经重复了,则cnt++
                CONTAINER[j].cnt++;
            ///printf("length是:%d\n",strlen(words));
            inserts(words);
            ///printf("过了insert\n");
            i=0;
        }
        if(ch=='#') break;
    }
    ///删除CONTAINER中重复的单词
}
//应用2统计所有出现过的单词词频
//找出所有单词并将其用query来求出每个单词的个数
char DEVIDE[50];
int Devide;
void Traves()
{
    root=new trieNode(' ');///建立根节点
    Text();
    printf("下面是所有单词以及出现的次数:\n");
    int i;
    for(i=0;i<container;i++)
    {
        printf("%s:",CONTAINER[i].words);
        printf("%d\n",CONTAINER[i].cnt);
    }
}

完整代码(包含DFS遍历)

在一些基础代码上我加上了一些简单的可操作界面,部分界面如下,可直接运行尝试

全部代码已经上传,可以直接点击链接或者下载资源获取

链接:Trie树的插入、查询、删除和部分应用(单词的查询、单词出现频率/DFS和非DFS两种遍历)

附上参考网址:https://www.bilibili.com/video/BV1KQ4y1N7Je ,上面我对于trie树的许多基础内容都是参考了这个up主讲的,看视频可能更清楚,需要的可以去看看。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号