trie树的基本操作

Trie树,又称前缀树,是一种用于高效存储和检索字符串的树形数据结构。它以键的前缀来决定节点的位置,常用于自动补全和拼写检查。插入、搜索和查找前缀的操作简洁高效,访问next数组的次数与单词长度相关,不受单词数量影响。虽然空间复杂度较高,但提供了快速的查询速度。
摘要由CSDN通过智能技术生成

trie树

介绍

Trie🌳(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完拼写检查

​ 在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。


一个例子形象化了解trie树

引入一个问题,存储多个单词,给定一个单词,查找是已存储,给定一个前缀(前缀也可以是个单词)判断该前缀是否是已存储的某个单词的前缀。

我们用trie树就可以巧妙地解决,想象以下,包含三个单词 “sea”,“sells”,“she” 的 Trie 会长啥样呢?

这个样子滴!
在这里插入图片描述

该trie树中有大量的空连接(大量的next指针并没有指向什么)因此我们可以这么表示

在这里插入图片描述

看到这里的小伙伴们是不是已经对trie树有了一个形象化的理解了呢,接下来我们介绍一下这个26叉树的具体操作(对于上文提出的问题而言,说这是个26叉树也不奇怪,哈哈,)学会了这个什么二叉树一定不在话下了把。继续读下去吧


Trie树的基本操作(存储单词,查找单词,查找前缀)

一下都以C++语言进行描述,在文末会给出整合的java语言代码

首先我们给出trie树的结构体(类)

class Trie {
private:
    bool isEnd;
    vector<Trie*> next;
public:
    Trie():next(26),isEnd(false){}
    //方法将在下文实现...
};

接着分析三个方法,不想听我啰嗦的同学可以直接看代码呀。

第一个方法是存储单词,存储单词我们从根节点开始,比如word。从根节点开始,第一个字母是w,因此们根节点的26个next数组中对应下标为w-'a’的改指针一定有指向,因此我们new一个节点,让根节点的next[w-‘a’]指向新的节点,并且新节点不是最后一个单词,isEnd=false,依此类推直到最后一个节点把该节点的isEnd置于true这样一个单词就插入完毕啦。

void insert(string word){
    Trie* node = this;
    for (char ch:word){
        if(node->next[ch-'a']==NULL)
            node->next[ch=-'a']=new Trie();
        node=node->next[ch-'a'];
    }
    node->isEnd=true;
}

第二三个方法很像,查找单词和查找前缀,他们共同的部分都是根据给出的字符串遍历trie树,遍历的方法为,把字符串对应的字母转化为数组下标,然后如果该字母下标有所指向就继续,如果没有则直接判断该单词没有存储。不同的部分为,查找单词需要验证最后一个节点的isEnd是否为true如果是true才能判断该单词已经存储,而查找前缀就不需要判断(这里认为整个单词就是该单词的前缀)。因此二三方法可以这么写

Trie* searchprefix(string prefix){
    Trie* node=this;
    for (char ch:prefix){
        ch-='a';
        if(node->next[ch]==nullptr)
            return nullptr;
        node=node->next[ch];
    }
    return node;
}

bool search(string word){
    Trie* node= this->searchprefix(word);
    return node!=nullptr && node->isEnd;
}
bool startsWith(string profix){
    Trie* node = this->searchprefix(word);
    return node!=nullptr;
}

总结

通过上述分析和代码我们可以发现trie的一些性质

  1. trie的形状和单词的插入或删除无关(删除的话就是按单词找下去没有就不用删除,有的话就把最后一个节点的isEnd=false此时如果是叶子节点就删除该节点,然后回滚直至删除所有节点)也就是说对于任意给定的一组单词,Trie 的形状都是唯一的。
  2. 查找或插入一个长度为 L 的单词,访问 next 数组的次数最多为 L+1,和 Trie 中包含多少个单词无关。(这是非常重要的,保证了查询的效率)
  3. Trie 的每个结点中都保留着一个字母表,这是很耗费空间的。如果 Trie 的高度为 n,字母表的大小为 m,最坏的情况是 Trie 中还不存在前缀相同的单词,那空间复杂度就为 O(m^n)。在如今的大多算法优化中我们都是用空间换时间。

全部代码

C++语言

class Trie {
private:
    bool isEnd;
    vector<Trie*> next;
public:
    Trie():next(26),isEnd(false){}
    
    void insert(string word){
    Trie* node = this;
    for (char ch:word){
        if(node->next[ch-'a']==nullptr)
            node->next[ch=-'a']=new Trie();
        node=node->next[ch-'a'];
    }
    node->isEnd=true;
    }
    
    Trie* searchprefix(string prefix){
        Trie* node=this;
        for (char ch:prefix){
            ch-='a';
            if(node->next[ch]==nullptr)
                return nullptr;
            node=node->next[ch];
        }
        return node;
    }	
    bool search(string word){
        Trie* node= this->searchprefix(word);
        return node!=nullptr && node->isEnd;
    }
    bool startsWith(string profix){
        Trie* node = this->searchprefix(word);
        return node!=nullptr;
    }
};

java语言

calss Trie{
    private Trie[] next;
    private boolean isEnd;
    
    public Trie(){
        next=new Trie[26];
        isEnd=false;
    }
    
    public void insert(String word){
        Trie node=this;
        for (int i=0;i<word.length();i++){
            char ch=word.charAt(i);
            int index=ch-'a';
            if(node.next[index]==null)
                node.next[index]=new Trie();
        }
        node.isEnd=true;
    }
    
    private Trie searchprefix(String prefix){
        Trie node=this;
        for(int i=0;i<prefix.length();i++){
            char ch=prefix.charAt(i);
            int index=ch-'a';
            if(node.next[index]==null)
                return null;
            node=node.next[index];
        }
        return node;
    }
    public boolean search(String word){
        Trie node=searchprefix(word);
        return node!=null&& node.isEnd;
    }
    public boolean startsWith(String prefix){
        return searchprefix(prefix)!=null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值