【数据结构(C++实现)】:Trie_字典树_前缀树

目录

 

一、介绍

二、如何表示Trie的结点

        1.方法一:数组

        2.方法二:Map

三、性质

四、Trie结点的定义

五、Trie接口定义

六、插入字符串

七、搜索字符串

        1.搜索前缀

        2.搜索单词

        3.时间复杂度

八、完整代码

  • 一、介绍

        Trie,又称字典树或前缀树,是用来存储字符串的N叉树。该Tire中每一个结点都代表一个前缀或者字符串,而这个前缀或者字符串是由通往该结点的所有路径决定的。

《【数据结构(C++实现)】:Trie_字典树_前缀树》

                                                                                             图1.字典树

        如上图所示,是一棵字典树。

        从根结点出发,走最右边的分支,先到达y,此时代表前缀y;再到达e,此时代表前缀ye;最后走到s,代表字符串yes。

        再从根出发,走最左边的分支,先到达a,此时代表前缀a;再到达b,此时代表前缀ab;之后ab处分支出两条路,分布代表字符串abc和abd。这种情况下具有公共前缀的字符串只需要存储其不同的子字符串,而前缀只存储一次,这也是前缀树的由来。

        其中,根节点表示空字符串

  • 二、如何表示Trie的结点

  •         1.方法一:数组

                    如果假设我们的字符串只包含26个小写字母,那么每个节点最多有26个子结点,因此我们可以用指针数组(nextBranch)来表示其子节点。如下:

class TrieNode {
    TrieNode* nextBranch[N];
};

                    如果子节点包含某个字母,那么对应的指针不为空。以图1的根节点为例,其指针数组中只有nextBranch[0]、nextBranch[3]和nextBranch[24]不为NULL。

  •         2.方法二:Map

                    数组方法的缺点在于必须事先确定字符串中字符种类的数量,而且如果字符种类的数量太多,那么会十分浪费空间。此时可以使用Hashmap来实现。这样更省空间也更灵活,但是可能比数组稍慢一些。

class TrieNode {
    unordered_map<char, TrieNode*> nextBranch;
};
  • 三、性质

        1.由于字符串的公共前缀会统一保存,故节约内存;

        2.在trie上进行检索总是从根结点出发;

        3.根结点不包含字符,除根结点外的每一个结点都只代表一个字母;

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

        5.每个结点的所有子结点包含的字符都不相同;

  • 四、Trie结点的定义

/*字典树结点*/
class TrieNode{
public:
    /*结点最大分支树(由于这里假设所有输入是由26个小写字母组成,故MaxBranchNum为26)*/
    const static int MaxBranchNum = 26;
    /*指针数组,代表26个字母,如果包含第i个字母,那么nextBranch[i]!=NULL*/
    TrieNode* nextBranch[MaxBranchNum];
    /*是否是一个单词的结尾*/
    bool isEnd;
    /*当isEnd==true,该值保存对应的单词*/
    string word;
    /*该单词的出现次数*/
    int count;

    TrieNode():count(0),isEnd(false){
        memset(nextBranch,NULL,sizeof(TrieNode*)*MaxBranchNum);//初始化nextBranch数组
    };
    /*插入字母ch(相当于将ch在nextBranch中对应的指针实例化)*/
    void put(char ch){
        if(nextBranch[ch-'a']==NULL)nextBranch[ch-'a'] = new TrieNode();
    }
    /*获得字母ch对应的结点指针,当ch不存在时返回NULL*/
    TrieNode* get(char ch){return nextBranch[ch-'a'];}
    /*判断当前结点是否包含字母ch(相当于判断ch在nextBranch中对应的指针是否为NULL)*/
    bool containsKey(char ch){return nextBranch[ch-'a']!=NULL;}
    /*设置isEnd为true*/
    void setEnd(){isEnd = true;}
    /*判断是否是单词*/
    bool isWord(){return isEnd;}
};
  • 五、Trie接口定义

/*字典树*/
class Trie{
public:
    Trie(){root = new TrieNode();}
    ~Trie(){destory(this->root);}

    /*插入单词*/
    void insert(const string word);

    /*查找前缀prefix,如果查找成功则返回最后的结点,否则返回NULL*/
    TrieNode* searchPrefix(const string prefix);
    /*查找单词word,如果查找成功返回word的出现次数,否则返回0;*/
    int search(const string word);
    /*如果存在任意单词以prefix为前缀,则返回true,否则返回false*/
    bool startsWith(const string prefix);

    /*打印当前字典树中的所有单词*/
    void printALL();
    /*打印以prefix为前缀的所有单词*/
    void printPre(const string prefix);
private:
    TrieNode* root;
    /*销毁以root为根的子树*/
    void destory(TrieNode* root);
    /*打印以root为根的所有单词*/
    void print(TrieNode* root);
};
  • 六、插入字符串

/*插入单词*/
void Trie::insert(const string word){
    if(word.size()==0)return;
    for(int i=0;i<word.size();i++)//出现异常字符时直接返回
        if(word[i]<'a'||word[i]>'z')return;
    TrieNode* node = root;
    for(int i=0;i<word.size();i++){
        if(!node->containsKey(word[i]))
            node->put(word[i]);
        node = node->get(word[i]);
    }
    //此时的node指向最终的结点
    if(node->word.size()==0){
        node->word.assign(word);
    }
    node->count++;
    node->setEnd();
}

        时间复杂度:如果插入的字符串长度为m,那么时间复杂度为O(m).

  • 七、搜索字符串

  •         1.搜索前缀

                    由于所有结点的后代都与该结点相对应字符串有着公共的前缀。因此很容易搜索到有公共前缀的任意单词。所以,可以根据给定的前缀沿着树向下搜索,如果有找不到的结点,那么搜索失败;否则,搜索成功。

/*查找前缀prefix,如果查找成功则返回最后的结点,否则返回NULL*/
TrieNode* Trie::searchPrefix(const string prefix){
    TrieNode* node = root;
    for(int i=0;i<prefix.size();i++){
        if(prefix[i]<'a'||prefix[i]>'z')return NULL;
        if(node->containsKey(prefix[i]))
            node = node->get(prefix[i]);
        else
            return NULL;
    }
    return node;
}
  •         2.搜索单词

                    像搜索前缀一样,当搜索完成后会返回搜索的最终结点,可以通过方法isWord()进行判断其是否是单词,也可以通过判断count>0来判断是否是单词。

/*查找单词word,如果查找成功返回word的出现次数,否则返回0;*/
int Trie::search(const string word){
    TrieNode* node = searchPrefix(word);
    return node?node->count:0;
}
  •         3.时间复杂度

                    无论是查找前缀还是单词,其时间复杂度与插入相同,即O(m).

  • 八、完整代码


/*数据结构:字典树(前缀树)*/

#include <iostream>
#include <string.h>

using namespace std;
/*字典树结点*/
class TrieNode{
public:
    /*结点最大分支树(由于这里假设所有输入是由26个小写字母组成,故MaxBranchNum为26)*/
    const static int MaxBranchNum = 26;
    /*指针数组,代表26个字母,如果包含第i个字母,那么nextBranch[i]!=NULL*/
    TrieNode* nextBranch[MaxBranchNum];
    /*是否是一个单词的结尾*/
    bool isEnd;
    /*当isEnd==true,该值保存对应的单词*/
    string word;
    /*该单词的出现次数*/
    int count;

    TrieNode():count(0),isEnd(false){
        memset(nextBranch,NULL,sizeof(TrieNode*)*MaxBranchNum);//初始化nextBranch数组
    };
    /*插入字母ch(相当于将ch在nextBranch中对应的指针实例化)*/
    void put(char ch){
        if(nextBranch[ch-'a']==NULL)nextBranch[ch-'a'] = new TrieNode();
    }
    /*获得字母ch对应的结点指针,当ch不存在时返回NULL*/
    TrieNode* get(char ch){return nextBranch[ch-'a'];}
    /*判断当前结点是否包含字母ch(相当于判断ch在nextBranch中对应的指针是否为NULL)*/
    bool containsKey(char ch){return nextBranch[ch-'a']!=NULL;}
    /*设置isEnd为true*/
    void setEnd(){isEnd = true;}
    /*判断是否是单词*/
    bool isWord(){return isEnd;}
};
/*字典树*/
class Trie{
public:
    Trie(){root = new TrieNode();}
    ~Trie(){destory(this->root);}

    /*插入单词*/
    void insert(const string word);

    /*查找前缀prefix,如果查找成功则返回最后的结点,否则返回NULL*/
    TrieNode* searchPrefix(const string prefix);
    /*查找单词word,如果查找成功返回word的出现次数,否则返回0;*/
    int search(const string word);
    /*如果存在任意单词以prefix为前缀,则返回true,否则返回false*/
    bool startsWith(const string prefix);

    /*打印当前字典树中的所有单词*/
    void printALL();
    /*打印以prefix为前缀的所有单词*/
    void printPre(const string prefix);
private:
    TrieNode* root;
    /*销毁以root为根的子树*/
    void destory(TrieNode* root);
    /*打印以root为根的所有单词*/
    void print(TrieNode* root);
};

/*插入单词*/
void Trie::insert(const string word){
    if(word.size()==0)return;
    for(int i=0;i<word.size();i++)//出现异常字符时直接返回
        if(word[i]<'a'||word[i]>'z')return;
    TrieNode* node = root;
    for(int i=0;i<word.size();i++){
        if(!node->containsKey(word[i]))
            node->put(word[i]);
        node = node->get(word[i]);
    }
    //此时的node指向最终的结点
    if(node->word.size()==0){
        node->word.assign(word);
    }
    node->count++;
    node->setEnd();
}

/*查找前缀prefix,如果查找成功则返回最后的结点,否则返回NULL*/
TrieNode* Trie::searchPrefix(const string prefix){
    TrieNode* node = root;
    for(int i=0;i<prefix.size();i++){
        if(prefix[i]<'a'||prefix[i]>'z')return NULL;
        if(node->containsKey(prefix[i]))
            node = node->get(prefix[i]);
        else
            return NULL;
    }
    return node;
}

/*查找单词word,如果查找成功返回word的出现次数,否则返回0;*/
int Trie::search(const string word){
    TrieNode* node = searchPrefix(word);
    return node?node->count:0;
}

/*如果存在任意单词以prefix为前缀,则返回true,否则返回false*/
bool Trie::startsWith(const string prefix){
    return searchPrefix(prefix)!=NULL;
}

/*打印以root为根的所有单词*/
void Trie::print(TrieNode* root){
    if(root==NULL)return;
    TrieNode* node = root;
    if(root->word.size()!=0)cout<<root->word<<endl;
    for(int i=0;i<root->MaxBranchNum;i++)
        print(root->nextBranch[i]);
}
/*打印当前字典树中的所有单词*/
void Trie::printALL(){
    print(this->root);
}
/*打印以prefix为前缀的所有单词*/
void Trie::printPre(const string prefix){
    if(prefix.size()==0)printALL();
    else{
        TrieNode* node = root;
        for(int i=0;i<prefix.size();i++){
            if(node->containsKey(prefix[i]))node = node->get(prefix[i]);
            else return;
        }
        print(node);
    }
}
/*销毁以root为根的子树*/
void Trie::destory(TrieNode* root){
    if(root==NULL)return;
    for(int i=0;i<root->MaxBranchNum;i++)
        destory(root->nextBranch[i]);
    delete root;
    root = NULL;
}
int main(){
    Trie* a = new Trie();
    a->insert("hello");
    a->insert("hello");
    a->insert("hi");
    a->insert("beauty");
    a->insert("helloworld");
    a->insert("word");
    cout<<a->search("hello");//2
    a->printALL();
    a->printPre("h");
    delete a;
    return 0;
}

 

    原文作者:Trie树
    原文地址: https://blog.csdn.net/bqw18744018044/article/details/82502435
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞