概念:
字典樹又稱單詞查找樹,Trie樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。(百度百科)
圖解:
上圖爲一顆字典樹,它表示字符串:aa、aaeb、aaec、ad、b、ba、cb、ca、cac
藍色表示一個字符串的結束標記節點
設計:
節點定義:
如何定義上面的字典樹中的一個節點呢?
class TrieNode {
TrieNode[] children;
boolean isEnd;
}
children 表示當前節點的孩子們節點引用,isEnd 表示當前節點是否爲結束標記節點
另外說一下,TrieNode[] children 孩子們節點引用是有指定大小的,假如我們存儲的字符串都是小寫字母,這個大小爲 26
每個不同的數組下標表示一個字符,例如:children[0] 表示小寫 a 的節點引用
代碼實現:
public class Trie {
private TrieNode root;
private class TrieNode {
TrieNode[] children;
boolean isEnd;
}
Trie() {
root = new TrieNode();
root.children = new TrieNode[26];
root.isEnd = false;
}
/**
* 將單詞插入到 trie 樹中
* @param word 單詞
*/
public void insert(String word) {
int index = 0;
TrieNode ptr = root;
while (index < word.length()) {
int position = word.charAt(index) - 'a';
if (ptr.children[position] == null) {
TrieNode node = new TrieNode();
node.children = new TrieNode[26];
node.isEnd = false;
ptr.children[position] = node;
}
ptr = ptr.children[position];
index++;
}
ptr.isEnd = true;
}
/**
* 查找 trie 樹中是否存在該單詞
* @param word 單詞
* @return 存在返回 true,否則返回 false
*/
public boolean search(String word) {
int index = 0;
TrieNode ptr = root;
while (index < word.length()) {
int position = word.charAt(index) - 'a';
if (ptr.children[position] == null) {
return false;
}
ptr = ptr.children[position];
index++;
}
return ptr.isEnd;
}
/**
* 查找 trie 樹中是否有給定前綴開頭的單詞
* @param prefix 前綴
* @return 存在返回 true,否則返回 false
*/
public boolean startsWith(String prefix) {
int index = 0;
TrieNode ptr = root;
while (index < prefix.length()) {
int position = prefix.charAt(index) - 'a';
if (ptr.children[position] == null) {
return false;
}
ptr = ptr.children[position];
index++;
}
return true;
}
}
查找 trie 樹中所有單詞:
/**
* 查找 trie 樹中所有單詞
* @param trieNode 當前節點
* @param wordList 單詞列表
* @param word 臨時字母存儲區域
*/
public void getAllWord(TrieNode trieNode, List<String> wordList, StringBuilder word) {
for (int i = 0; i < 26; i++) {
if (trieNode.children[i] != null) {
word.append((char) (i + 'a'));
if (trieNode.children[i].isEnd) {
wordList.add(word.toString());
}
getAllWord(trieNode.children[i], wordList, word);
word.deleteCharAt(word.length() - 1);
}
}
}
查找 trie 樹中以給定前綴的單詞:
/**
* 查找 trie 樹中以給定前綴的單詞
* @param trieNode 當前節點
* @param wordList 單詞列表
* @param word 臨時字母存儲區域
* @param prefix 給定前綴
* @param index 前綴當前下標
*/
public void searchByPrefix(TrieNode trieNode, List<String> wordList, StringBuilder word, String prefix, int index) {
if (index < prefix.length()) {
char ch = prefix.charAt(index);
int position = ch - 'a';
if (trieNode.children[position] != null) {
word.append((char) (position + 'a'));
if (index == prefix.length() - 1 && trieNode.children[position].isEnd) {
wordList.add(word.toString());
}
searchByPrefix(trieNode.children[position], wordList, word, prefix, index + 1);
word.deleteCharAt(word.length() - 1);
}
} else {
for (int i = 0; i < 26; i++) {
if (trieNode.children[i] != null) {
word.append((char) (i + 'a'));
if (trieNode.children[i].isEnd) {
wordList.add(word.toString());
}
searchByPrefix(trieNode.children[i], wordList, word, prefix, index + 1);
word.deleteCharAt(word.length() - 1);
}
}
}
}
測試:
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("aa");
trie.insert("aaeb");
trie.insert("aaec");
trie.insert("ad");
trie.insert("b");
trie.insert("ba");
trie.insert("cb");
trie.insert("ca");
trie.insert("cac");
System.out.println("獲取 trie 樹中所有單詞:");
LinkedList<String> wordList = new LinkedList<>();
trie.getAllWord(trie.root, wordList, new StringBuilder());
for (String word : wordList) {
System.out.println(word);
}
System.out.println("獲取 trie 樹中給定前綴 aa 的單詞:");
wordList = new LinkedList<>();
trie.searchByPrefix(trie.root, wordList, new StringBuilder(), "aa", 0);
for (String word : wordList) {
System.out.println(word);
}
}
總結:
trie 樹精華全在代碼中,多多看看上面給出的代碼
本人寫的也不一定很好,如果有不足之處,請在下面評論,我看到後會一一改正
如果有不懂的也可以在下方評論,大家相互討論學習 ^_^