实现Trie【前缀树(字典树)】

Trie,又称前缀树或字典树,是一棵有根树,其每个节点包含以下字段:

  • 指向子节点的指针数组children。对于小写字母而言,数组长度为26,即小写英文字母的数量。此时children[0] 对应小写字母 a,children[1] 对应小写字母 b,…,[25]children[25] 对应小写字母 z。
  • 布尔字段isEnd,表示该节点是否为字符串的结尾。

插入字符串
我们从字典树的根开始,插入字符串。对于当前字符对应的子节点,有两种情况:

  • 子节点存在。沿着指针移动到子节点,继续处理下一个字符。
  • 子节点不存在。创建一个新的子节点,记录在children 数组的对应位置上,然后沿着指针移动到子节点,继续搜索下一个字符。

重复以上步骤,直到处理字符串的最后一个字符,然后将当前节点标记为字符串的结尾。

查找前缀
我们从字典树的根开始,查找前缀。对于当前字符对应的子节点,有两种情况:

  • 子节点存在。沿着指针移动到子节点,继续搜索下一个字符。
  • 子节点不存在。说明字典树中不包含该前缀,返回空指针。

重复以上步骤,直到返回空指针或搜索完前缀的最后一个字符。

若搜索到了前缀的末尾,就说明字典树中存在该前缀。此外,若前缀末尾对应节点的isEnd 为真,则说明字典树中存在该字符串。
代码如下:

class Trie {
private:
    bool isEnd;
    Trie* next[26];
public:
    Trie() { //初始化前缀树对象
        isEnd=false;
        memset(next,0,sizeof(next));
    }
    
    void insert(string word) { //插入
        Trie* node=this;
        for(char c:word){
            if(node->next[c-'a']==NULL) node->next[c-'a']=new Trie();
            node=node->next[c-'a'];
        }
        node->isEnd=true;
    }
    
    bool search(string word) { //如果word在前缀树中,返回true
        Trie* node=this;
        for(char c:word){
            node=node->next[c-'a'];
            if(node==NULL){
                return false;
            }
        }
        return node->isEnd;
    }
    
    bool startsWith(string prefix) { //如果prefix为已经插入的word前缀之一,返回true
        Trie* node=this;
        for(char c:prefix){
            node=node->next[c-'a'];
            if(node==NULL) return false;
        }
        return true;
    }
};

下面看一道题目。题目是P472连接词
实现Trie【前缀树(字典树)】_第1张图片
需要频繁匹配字符串的题目,可以尝试用字典树解决。可以先按照字符的多少排序,先遍历字符量小的,然后对于每个字符串,我们都遍历一次字典树,会有两种情况:

  • 中途遇到空节点,说明当前匹配不成立,返回false,回到上层
  • 中途遇到一个isEnd为true的单词尾节点,则说明当前匹配的单词可能是构成字符串s的单词之一。因此,我们以此为界,从字符串根节点开始继续遍历之后的部分。

如果单词匹配成功,说明这个单词完全包含在字典树中,无需再插入。如果匹配失败,则将单词插入字符串。代码如下:

struct TrieNode{ //建立字典树结构体
    bool isEnd;
    vector<TrieNode*>next;
    TrieNode(){ //初始化
        isEnd=false;
        next=vector<TrieNode*>(26,nullptr);
    };
};
TrieNode* root; //字典树根节点
vector<string>ans; 
void insert(string& word){ //插入字典树操作
    TrieNode* node=root;
    for(char c:word){
        if(node->next[c-'a']==nullptr){
            node->next[c-'a']=new TrieNode();
        }
        node=node->next[c-'a'];
    }
    node->isEnd=true; //注意返回的值
}
bool dfs(string& s,int pos){
    if(pos==s.size()) return true; //字符串s已经全部匹配完了
    TrieNode* node=root; //从根节点开始遍历
    for(int i=pos;i<s.size();i++){
        char c=s[i];
        if(node->next[c-'a']==nullptr) return false; //不匹配直接返回false
        node=node->next[c-'a']; //继续向下遍历
        //如果当前结点为某个单词的末尾,继续遍历后面
        if(node->isEnd==true&&dfs(s,i+1)) return true; //如果后面也匹配完成,返回true
    }
    return false;
}

static bool cmp(const string& a,const string& b){
    return a.size()<b.size();
}
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
    sort(words.begin(),words.end(),cmp); //字符串按长度升序排列
    root=new TrieNode();

    for(auto& w:words){
        if(w.size()==0) continue;
        if(dfs(w,0)) ans.push_back(w); //找到满足条件的就放入ans
        else insert(w); //否则插入到字典树中
    }
    return ans;
}

你可能感兴趣的:(链表,数据结构,b树,字典)