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;
}
};