Trie树(字典树)相关概念以及实现和练习

Trie树(字典树)相关概念以及实现和练习

Trie树基本概述

Trie树,又称字典树或前缀树,是一种有序的、用于统计、排序和存储字符串的数据结构,它的关键字不是保存在节点中,而是由节点在树中的位置决定
在这里插入图片描述
一个节点的所有子孙都具有相同的前缀,也就是这个节点对应的字符串,根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才具有相关的值。

trie树最大优点是利用字符串的公共前缀来减少存储空间和查询时间,从而最大限度地减少无谓的字符串比较,非常高效。

trie的应用:单词自动补全、拼写检查、最长前缀匹配等

Trie的结点结构:

struct TrieNode
{
     bool is_end; //表示是否是一个字符串的结尾
     TrieNode *child[26]; //a-z 26个字母
     TrieNode():is_end(false){
         for(int i=0;i<26;i++)
             child[i]='a'+i;
     }  	
};

208. 实现 Trie (前缀树)

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true

分析:

Trie树的单词插入:

  • 构建ptr指针指向root
  • 逐个遍历字符串中的各个字符
    • 计算下标pos=正在遍历的字符-‘a’
    • 如果ptr指向的结点的第pos个孩子为假:
      • 创建该结点的第pos个孩子
    • ptr指向该结点的第pos个孩子
  • 标记ptr指向的结点的is_end为true

Trie树的搜索:

  • 使用ptr指针指向root
  • 逐个遍历字符串中的各个字符
    • 计算下标pos=正在遍历的字符-‘a’
    • 如果ptr指向的结点不存在,则返回false
    • 否则ptr指向该结点的第pos个孩子
  • 返回ptr指向的结点的is_end

Trie树搜索是否存在给定前缀:

  • 与搜索思路一致,遍历每个字符,直至遍历结束。期间,如果ptr指向的结点不存在则返回false。如果遍历该前缀结束,则说明至少该前缀属于一个单词,因为如果不属于任何单词的话是没办法遍历至最后一个字母的。
struct TrieNode
{
     bool is_end; //表示是否是一个字符串的结尾
     TrieNode *child[26];   //位置即字符,不需要另外存储
     TrieNode():is_end(false){
         for(int i=0;i<26;i++)
             child[i]=0;
     }  	
};
class Trie {
public:
    /** Initialize your data structure here. */
    Trie() {

    }
    ~Trie()
    {
        for(int i=0;i<node_vec.size();i++)
        {
            delete node_vec[i];
        }
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) 
    {
        TrieNode* ptr=&root;
        for(auto s:word)
        {
            int pos=s-'a'; //位置
            if(!ptr->child[pos]) //所指的孩子结点不存在
            {
                ptr->child[pos]=new_node(); //创建新结点
            }
            ptr=ptr->child[pos];
        }
        ptr->is_end=true; //表示以该位置结尾的路径有单词
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) 
    {
        TrieNode* ptr=&root;
        for(auto s:word)  //遍历,如果遍历时发现该路径不存在,也就是该字符不存在,则返回false
        {
            int pos=s-'a';
            if(!ptr->child[pos])
                return false;
            ptr=ptr->child[pos];
        }
        return ptr->is_end==true;  //遍历至结尾了只需要判断该位置是否是一个string的结尾即可
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) 
    {
        TrieNode* ptr=&root;
        for(auto s:prefix)  //遍历,如果遍历时发现该路径不存在,也就是该字符不存在,则返回false
        {
            int pos=s-'a';
            if(!ptr->child[pos])
                return false;
            ptr=ptr->child[pos];
        }
        return true;
    }
    private:
        TrieNode* new_node()
        {
            TrieNode *node=new TrieNode();
            node_vec.push_back(node);
            return node;
        }
        vector<TrieNode*> node_vec;
        TrieNode root;
};

648. 单词替换

在英语中,我们有一个叫做 词根(root)的概念,它可以跟着其他一些词组成另一个较长的单词——我们称这个词为 继承词(successor)。例如,词根an,跟随着单词 other(其他),可以形成新的单词 another(另一个)。

现在,给定一个由许多词根组成的词典和一个句子。你需要将句子中的所有继承词用词根替换掉。如果继承词有许多可以形成它的词根,则用最短的词根替换它。

你需要输出替换之后的句子。

示例:
输入:dict(词典) = ["cat", "bat", "rat"] sentence(句子) = "the cattle was rattled by the battery"
输出:"the cat was rat by the bat"

分析:使用字典树

  • 建立字典树(字典树至少包括两个功能:插入单词和查找某一单词的词根)
  • 将所有词典中的单词插入字典树中
  • 分割句子中的单词,然后用词根替换
  • 返回最终的句子

代码:

struct TrieNode
{
     bool is_end; //表示是否是一个字符串的结尾
     TrieNode *child[26];   //位置即字符,不需要另外存储
     TrieNode():is_end(false){
         for(int i=0;i<26;i++)
             child[i]=0;
     }  	
};
class Trie {
public:
    /** Initialize your data structure here. */
    Trie() {

    }
    ~Trie()
    {
        for(int i=0;i<node_vec.size();i++)
        {
            delete node_vec[i];
        }
    }
    
    /** Inserts a word into the trie. */
    void insert(string word)  //向字典树中插入单词
    {
        TrieNode* ptr=&root;
        for(auto s:word)
        {
            int pos=s-'a'; //位置
            if(!ptr->child[pos]) //所指的孩子结点不存在
            {
                ptr->child[pos]=new_node(); //创建新结点
            }
            ptr=ptr->child[pos];
        }
        ptr->is_end=true; //表示以该位置结尾的路径有单词
    }
    
    /** Returns if the word is in the trie. */
    string search(string word) 
    {
        TrieNode* ptr=&root;
        string result="";
        for(auto s:word)  //遍历,如果某个前缀的is_end是true则返回该前缀
        {
            int pos=s-'a';
            if(!ptr->child[pos])
                return word;
            ptr=ptr->child[pos];
            result+=s;
            if(ptr->is_end==true)
                return result;
        }
        return result;  //遍历至结尾了只需要判断该位置是否是一个string的结尾即可
    }
    private:
        TrieNode* new_node()
        {
            TrieNode *node=new TrieNode();
            node_vec.push_back(node);
            return node;
        }
        vector<TrieNode*> node_vec;
        TrieNode root;
};
class Solution {
public:
    string replaceWords(vector<string>& dict, string sentence) 
    {
        Trie *root=new Trie();
        for(int i=0;i<dict.size();i++)
        root->insert(dict[i]); //构建字典树
        string word="";
        string result="";
        for(int i=0;i<sentence.size();i++)  
        {
            if(sentence[i]!=' ') //如果不是空格则继续加单词
            {
                word+=sentence[i];
            }
            if(sentence[i]==' ')  //如果遇到空格说明完整单词,将该单词替换即可
            {
                result+=root->search(word);
                result+=' ';
                word="";
            }
        }
        result+=root->search(word);  //最后一个单词替换
        return result;
        
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章