LeetCode刷題MEDIM篇Implement Trie (Prefix Tree)
阿新 • • 發佈:2019-01-09
題目
Implement a trie with insert
, search
, and startsWith
methods.
Example:
Trie trie = new Trie(); trie.insert("apple"); trie.search("apple"); // returns true trie.search("app"); // returns false trie.startsWith("app"); // returns true trie.insert("app"); trie.search("app"); // returns true
Note:
- You may assume that all inputs are consist of lowercase letters
a-z
. - All inputs are guaranteed to be non-empty strings.
十分鐘嘗試
一個新的資料結構,實現一個字典樹/字首樹,是一個多叉樹。補充一下知識點:
上圖是一棵Trie樹,表示了關鍵字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。從上圖可以歸納出Trie樹的基本性質:
- 根節點不包含字元,除根節點外的每一個子節點都包含一個字元。
- 從根節點到某一個節點,路徑上經過的字元連線起來,為該節點對應的字串。
- 每個節點的所有子節點包含的字元互不相同。
Trie樹的核心思想是空間換時間,利用字串的公共字首來減少無謂的字串比較以達到提高查詢效率的目的。
相比於hash
- 如果根據共同字首尋找所有的key,hash效能不好,因為hash碰撞
- 按照字典排序列舉字串
- 隨著hash size增加,出現碰撞概率加大,影響效能
平衡樹稍後對比
應用
- 字串查詢:
檢索/查詢功能是Trie樹最原始的功能。思路就是從根節點開始一個一個字元進行比較:如果沿路比較,發現不同的字元,則表示該字串在集合中不存在。如果所有的字元全部比較完並且全部相同,還需判斷最後一個節點的標誌位(標記該節點是否代表一個關鍵字)
- 詞頻統計:為了實現詞頻統計,我們修改了節點結構,用一個整型變數
count
來計數。對每一個關鍵字執行插入操作,若已存在,計數加1,若不存在,插入後count
置1 - 字串排序:思路也很簡單:遍歷一次所有關鍵字,將它們全部插入trie樹,樹的每個結點的所有兒子很顯然地按照字母表排序,然後先序遍歷輸出Trie樹中所有關鍵字即可
- 字首匹配:
找出一個字串集合中所有以
ab
開頭的字串。我們只需要用所有字串構造一個trie樹,然後輸出以a->b->
開頭的路徑上的關鍵字即可。trie樹字首匹配常用於搜尋提示。如當輸入一個網址,可以自動搜尋出可能的選擇。當沒有完全匹配的搜尋結果,可以返回字首最相似的可能。
字首樹節點結構
1. 子節點的指標陣列,指向所有的子節點,因為是多叉樹,不同於二叉樹,只有left和right
2 每個節點包含一個boolean值的field,表示是否是key的結束,或者只是一個prefix。
我看了答案的思路後,自己寫了一般,需要注意的一點是,search和startPrefix的不同,search需要判斷最後的節點是不是end,也就是葉子節點,而startPrefix不需要。其他我的程式碼命名更加適合自己的理解。
class TrieNode{
private TrieNode[] childrens;
private boolean isEnd;
public TrieNode(){
childrens=new TrieNode[26];
}
public boolean hasChild(char ch){
return childrens[ch-'a']!=null;
}
public void addChild(char ch){
childrens[ch-'a']=new TrieNode();
}
public TrieNode getChild(char ch){
return childrens[ch-'a'];
}
public void setEnd(){
isEnd=true;
}
public boolean isEnd(){
return isEnd;
}
}
class Trie {
private TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root=new TrieNode();
}
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode currNode=root;
if(word==null||word.length()==0){
return;
}
for(int i=0;i<word.length();i++){
char currChar=word.charAt(i);
if(!currNode.hasChild(currChar)){
currNode.addChild(currChar);
}
TrieNode nextNode=currNode.getChild(currChar);
currNode=nextNode;
}
currNode.setEnd();
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode currNode=root;
if(word==null||word.length()==0){
return false;
}
for(int i=0;i<word.length();i++){
char currChar=word.charAt(i);
if(!currNode.hasChild(currChar)){
return false;
}
TrieNode nextNode=currNode.getChild(currChar);
currNode=nextNode;
}
return currNode.isEnd();
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TrieNode currNode=root;
if(prefix==null||prefix.length()==0){
return false;
}
for(int i=0;i<prefix.length();i++){
char currChar=prefix.charAt(i);
if(!currNode.hasChild(currChar)){
return false;
}
TrieNode nextNode=currNode.getChild(currChar);
currNode=nextNode;
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/