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