Java 实现 Trie (前缀树)
LeetCode:https://leetcode-cn.com/problems/implement-trie-prefix-tree/
什么是前綴樹
Trie(發音類似 “try”)或者說 前綴樹 是一種樹形數據結構,用于高效地存儲和檢索字符串數據集中的鍵。這一數據結構有相當多的應用情景,例如自動補完和拼寫檢查。
- Trie() 初始化前綴樹對象。
- void insert(String word) 向前綴樹中插入字符串 word 。
- boolean search(String word) 如果字符串 word 在前綴樹中,返回 true(即,在檢索之前已經插入);否則,返回 false 。
- boolean startsWith(String prefix) 如果之前已經插入的字符串 word 的前綴之一為 prefix ,返回 true ;否則,返回 false 。
實現前綴樹
摘自:https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/208-shi-xian-trie-qian-zhui-shu-bao-gua-insert-sea/
這是一種叫做 單詞查找樹 的結構。它由字符串鍵中所有的字符構造而成,允許使用被查找鍵中的字符進行查找。其中包括插入、查找、刪除,尋找前綴等操作。接下來先介紹Trie樹的基本性質。
先看下面的例子:
現在有5個word,分別為by,by,hello,heat,the。所構成的TrieTree如圖所示,其中包含一個根節點,值為空,跟幾點所連接的是每個word的第一個字符,每個字符按照同樣的方式生成與之連接的字符的TrieTree,在每個word的最末處,表示該word出現了幾次。例如:“b”處為0,表示"b"這個單詞沒有出現過?!皔”處為2,表示“by”這個單詞出現了兩次。
單詞查找樹之所以有這樣的,是由于我們對于其結點的特殊定義。單詞查找樹的每一個節點都包含了下一個可能出現的字符的鏈接。從根節點開始,首先經過的是word的首字母所對應的鏈接,在下一個節點中沿著第二個字符所對應的鏈接繼續前進,在第二個節點中沿著第三個字符所對應的鏈接向前,這樣到達最后一個字符所指向的節點就結束。接下來我們來看對其節點的定義。
TrieNode
定義單詞查找樹的結點為:
public class TrieNode{public int path;public int end;public HashMap<Character, TrieNode> next;public TrieNode(){path = 0;end = 0;next = new HashMap<>();} }- path:表示當前節點所能鏈接到其他節點的個數(在刪除操作中會用到)
- end:表示以當前節點為結尾的單詞的個數
- next:HashMap<Character, TrieNode>類型,表示當前節點能鏈接到的所有節點。
這里next同樣可以使用字符數組來表示,例如字符的范圍是小寫字母,可以以’a’~'z’字符作為索引,這樣相比起哈希表來會提高查找速度,但每一個點都含有一個值和26個鏈接,這樣會多出好多空節點,如圖所示:
Insert 操作
如同鏈表的生成過程一樣,從根節點開始,如果根節點所連接的節點中沒有當前字符,則生成一個值為當前字符的新節點,插入其中;如果有當前字符,則則繼續進行匹配,并在過程中對每一個匹配到的節點path進行計數,重復上述過程,直到插完最后一個字符,并在最后一個字符的節點end進行計數,表示已經該單詞的插入操作已經完成。
public void insert(String word){if(word == null || word.equals("")) return ;TrieNode node = root;for(int i = 0; i<word.length(); i++){char ch = word.charAt(i);if(!node.next.containsKey(ch)) {node.next.put(ch,new TrieNode());}node = node.next.get(ch);node.path++;}node.end++; }Search 操作
同insert操作基本相同,由于我這里使用的是Hashmap進行的節點存儲,故如果在匹配的過程中沒能匹配到,則表示搜索失敗,匹配到后最后一個字符時,如果當前end值不為零,則表示匹配成功。
public boolean search(String word){if(word == null || word.equals("")) return false;TrieNode node = root;for(int i = 0; i<word.length(); i++){char ch = word.charAt(i);if(!node.next.containsKey(ch)) return false;node = node.next.get(ch);}if(node.end == 0) return false;return true; }startwith 操作
同search操作基本相同,只是這里判斷到最后一個字符的時候,不需要判斷end值。因為這里只需要檢查前綴是否存在。
public boolean startsWith(String word){if(word == null || word.equals("")) return false;TrieNode node = root;for(int i = 0; i<word.length(); i++){char ch = word.charAt(i);if(!node.next.containsKey(ch)) return false;node = node.next.get(ch);}return true; }delete 操作
delete操作同上述操作大致相同,這里需要使用到path變量,回憶一下,path:表示當前節點所能鏈接到其他節點的個數。還以五個單詞為例,例如刪除’the’,當匹配到‘t’時,由于進行path–操作后,此時判斷path為0,過可直接將當前節點置空,后面的數據則不用再去遍歷即可。java中的垃圾回收機制會處理其他的被斷開的節點,如果在C++中,則需要全部匹配,之后調用析構函數去操作。
public void delete(String word){if(word == null || word.equals("") || !search(word)) return ;TrieNode node = root;for (int i = 0; i < word.length(); i++) {char ch = word.charAt(i);if(--node.next.get(ch).path == 0){node.next.remove(ch);return;}node = node.next.get(ch);}node.end--; }代碼
import java.util.HashMap;public 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) {if (word == null || word.equals("")) {return;}TrieNode node = root;for (int i = 0; i < word.length(); i++) {char ch = word.charAt(i);if (!node.next.containsKey(ch)) {node.next.put(ch, new TrieNode());}node = node.next.get(ch);node.path++;}node.end++;}/*** Returns if the word is in the trie.*/public boolean search(String word) {if (word == null || word.equals("")) {return false;}TrieNode node = root;for (int i = 0; i < word.length(); i++) {char ch = word.charAt(i);if (!node.next.containsKey(ch)) {return false;}node = node.next.get(ch);}if (node.end == 0) {return false;}return true;}/*** Returns if there is any word in the trie that starts with the given prefix.*/public boolean startsWith(String prefix) {if (prefix == null || prefix.equals("")) {return false;}TrieNode node = root;for (int i = 0; i < prefix.length(); i++) {char ch = prefix.charAt(i);if (!node.next.containsKey(ch)) {return false;}node = node.next.get(ch);}return true;}public void delete(String word) {if (word == null || word.equals("") || !search(word)) {return;}TrieNode node = root;for (int i = 0; i < word.length(); i++) {char ch = word.charAt(i);if (--node.next.get(ch).path == 0) {node.next.remove(ch);return;}node = node.next.get(ch);}node.end--;}public class TrieNode {public int path;public int end;public HashMap<Character, TrieNode> next;public TrieNode() {this.path = 0;this.end = 0;next = new HashMap<>();}} }/*** 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);*/總結
以上是生活随笔為你收集整理的Java 实现 Trie (前缀树)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十九、CI框架之数据库操作delete用
- 下一篇: Detected both log4j-