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连接词
需要频繁匹配字符串的题目,可以尝试用字典树解决。可以先按照字符的多少排序,先遍历字符量小的,然后对于每个字符串,我们都遍历一次字典树,会有两种情况:
- 中途遇到空节点,说明当前匹配不成立,返回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;
}