Trie 樹 (字首樹, 字典樹)
阿新 • • 發佈:2020-12-16
參考
字典樹(字首樹)Trie樹(字典樹,字首樹,鍵樹)分析詳解Trie Tree 的實現 (適合初學者)https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/資料結構之Hash樹概述
字典樹(TrieTree),又稱單詞查詢樹或鍵樹,是一種樹形結構,是一種雜湊樹的變種。Trie的核心思想是空間換時間,利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。它有3個基本性質:根節點不包含字元,除根節點外每一個節點都只包含一個字元。
從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串。
每個節點的所有子節點包含的字元都不相同。
應用
Trie樹典型應用是用於快速檢索(最長字首匹配),自動補全,拼寫檢查,統計,排序和儲存大量的字串,所以經常被搜尋引擎系統用於文字詞頻統計,搜尋提示等場景。它的優點是最大限度地減少無謂的字串比較,查詢效率比較高。Trie樹與二叉搜尋樹
資料規模為n時,二叉搜尋樹插入、查詢、刪除操作的時間複雜度通常只有O(logn),最壞情況下整棵樹所有的節點都只有一個子節點,退變成一個線性表,此時插入、查詢、刪除操作的時間複雜度是O(n)。
通常情況下,Trie樹的高度n要遠大於搜尋字串的長度m,故查詢操作的時間複雜度通常為O(m),最壞情況下(當字串非常長)的時間複雜度才為O(n)。很容易看出,Trie樹最壞情況下的查詢也快過二叉搜尋樹。
Trie樹與Hash表
既然有了其他的資料結構,如平衡樹和雜湊表,使我們能夠在字串資料集中搜索單詞。為什麼我們還需要 Trie 樹呢?儘管雜湊表可以在 O(1) 時間內尋找鍵值,卻無法高效的完成以下操作:- 找到具有同一字首的全部鍵值。
- 按詞典序列舉字串的資料集。
定義類 Trie
正常的樹節點定義是怎麼樣的
struct TreeNode { VALUETYPE value; //結點值 TreeNode* children[NUM]; //指向孩子結點 };
下面是Trie的結點定義,體會二者的不同
class Trie {
boolean isEnd = false; // 標記是否為最終結點
Trie[] next; // 所有孩子結點
/** Initialize your data structure here. */
public Trie() {
next = new Trie[26]; // 26個孩子結點
}
}
插入
描述:向 Trie 中插入一個單詞 word
實現:這個操作和構建連結串列很像。首先從根結點的子結點開始與 word 第一個字元進行匹配,一直匹配到字首鏈上沒有對應的字元,這時開始不斷開闢新的結點,直到插入完 word 的最後一個字元,同時還要將最後一個結點isEnd = true;,表示它是一個單詞的末尾。
public void insert(String word) {
// 有則共享且繼續向下遍歷,否則新建結點
Trie node = this; // node指標用來遍歷
for(char ch : word.toCharArray()){
int index = ch - 'a';
if(node.next[index] == null){ // 新建結點
node.next[index] = new Trie();
}
node = node.next[index]; // 指標後移
}
node.isEnd = true; // 標記為最終結點
}
查詢
描述:查詢 Trie 中是否存在單詞 word
實現:從根結點的子結點開始,一直向下匹配即可,如果出現結點值為空就返回 false,如果匹配到了最後一個字元,那我們只需判斷 node->isEnd即可。
public boolean find(String word) {
// 有則共享且繼續向下遍歷,否則新建結點
Trie node = this; // node指標用來遍歷
for(char ch : word.toCharArray()){
int index = ch - 'a';
if(node.next[index] == null){ // 新建結點
return false;
}
node = node.next[index]; // 指標後移
}
return node.isEnd;
}
字首匹配
描述:判斷 Trie 中是或有以 prefix 為字首的單詞
實現:和 search 操作類似,只是不需要判斷最後一個字元結點的isEnd,因為既然能匹配到最後一個字元,那後面一定有單詞是以它為字首的
public boolean startsWith(String prefix) {
// 和search方法其實差不多,但是結束判斷後直接返回true, 因為是判斷字首而已
Trie node = this;
for(char ch : prefix.toCharArray()){
int index = ch - 'a';
if(node.next[index] == null){
return false;
}
node = node.next[index]; // 指標後移
}
return true;
}
總結通過以上介紹和程式碼實現我們可以總結出 Trie 的幾點性質:
- Trie 的形狀和單詞的插入或刪除順序無關,也就是說對於任意給定的一組單詞,Trie 的形狀都是唯一的。
- 查詢或插入一個長度為 L 的單詞,訪問 next 陣列的次數最多為 L+1,和 Trie 中包含多少個單詞無關。
- Trie 的每個結點中都保留著一個字母表,這是很耗費空間的。如果 Trie 的高度為 n,字母表的大小為 m,最壞的情況是 Trie 中還不存在字首相同的單詞,那空間複雜度就為 O(m^n)。