Trie樹的Java實現
阿新 • • 發佈:2019-01-09
第一部分、Trie樹
1.1、什麼是Trie樹
Trie樹,即字典樹,又稱單詞查詢樹或鍵樹,是一種樹形結構,是一種雜湊樹的變種。典型應用是用於統計和排序大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:最大限度地減少無謂的字串比較,查詢效率比雜湊表高。
Trie的核心思想是空間換時間。利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。
它有3個基本性質:
- 根節點不包含字元,除根節點外每一個節點都只包含一個字元。
- 從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串。
- 每個節點的所有子節點包含的字元都不相同。
好比假設有b,abc,abd,bcd,abcd,efg,hii 這6個單詞,我們構建的樹就是如下圖這樣的
如上圖所示,對於每一個節點,從根遍歷到他的過程就是一個單詞,如果這個節點被標記為紅色,就表示這個單詞存在,否則不存在。
那麼,對於一個單詞,我只要順著他從根走到對應的節點,再看這個節點是否被標記為紅色就可以知道它是否出現過了。
把這個節點標記為紅色,就相當於插入了這個單詞。
1.2、字首查詢
“比如說對於某一個單詞,我們要詢問它的字首是否出現過。這樣hash就不好搞了,而用trie還是很簡單“。下面,咱們來看看這個字首查詢問題: 已知n個由小寫字母構成的平均長度為10的單詞,判斷其中是否存在某個串為另一個串的字首子串。下面對比3種方法:- 最容易想到的:即從字串集中從頭往後搜,看每個字串是否為字串集中某個字串的字首,複雜度為O(n^2)。
- 使用hash:我們用hash存下所有字串的所有的字首子串,建立存有子串hash的複雜度為O(n*len),而查詢的複雜度為O(n)* O(1)= O(n)。
- 使用trie:因為當查詢如字串abc是否為某個字串的字首時,顯然以b,c,d....等不是以a開頭的字串就不用查找了。所以建立trie的複雜度為O(n*len),而建立+查詢在trie中是可以同時執行的,建立的過程也就可以成為查詢的過程,hash就不能實現這個功能。所以總的複雜度為O(n*len),實際查詢的複雜度也只是O(len)。(說白了,就是Trie樹的平均高度h為len,所以Trie樹的查詢複雜度為O(h)=O(len)。好比一棵二叉平衡樹的高度為logN,則其查詢,插入的平均時間複雜度亦為O(logN))。有了這樣一種資料結構,我們可以用它來儲存一個字典,要查詢改字典裡是否有相應的詞,是否非常的方便呢?我們也可以做智慧提示,我們把使用者已經搜尋的詞存在Trie裡,每當使用者輸入一個詞的時候,我們可以自動提示
1.3、Trie樹的應用
除了本文引言處所述的問題能應用Trie樹解決之外,Trie樹還能解決下述問題:- 3、有一個1G大小的一個檔案,裡面每一行是一個詞,詞的大小不超過16位元組,記憶體限制大小是1M。返回頻數最高的100個詞。
- 9、1000萬字符串,其中有些是重複的,需要把重複的全部去掉,保留沒有重複的字串。請怎麼設計和實現?
- 10、 一個文字檔案,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞,請給出思想,給出時間複雜度分析。
- 13、尋找熱門查詢:搜尋引擎會通過日誌檔案把使用者每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255位元組。假設目前有一千萬個記錄,這些查詢串的重複讀比較高,雖然總數是1千萬,但是如果去除重複和,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的使用者越多,也就越熱門。請你統計最熱門的10個查詢串,要求使用的記憶體不能超過1G。
(1) 請描述你解決這個問題的思路;
(2) 請給出主要的處理流程,演算法,以及演算法的複雜度。
Java程式碼
package TrieDemo;
import java.util.LinkedList;
/**
* Created by fernando on 12/3/16.
*/
public class Node {
char content; // the character in the node
boolean isEnd; // whether the end of the words
int count; // the number of words sharing this character
LinkedList<Node> childList; // the child list
public Node(char c) {
childList = new LinkedList<Node>();
isEnd = false;
content = c;
count = 0;
}
public Node subNode(char c) {
if (childList != null) {
for (Node eachChild : childList) {
if (eachChild.content == c) {
return eachChild;
}
}
}
return null;
}
}
package TrieDemo;
/**
* Created by fernando on 12/3/16.
*/
public class Trie {
private Node root;
public Trie() {
root = new Node(' ');
}
public void insert(String word) {
if (search(word) == true) return;
Node current = root;
for (int i = 0; i < word.length(); i++) {
Node child = current.subNode(word.charAt(i));
if (child != null) {
current = child;
} else {
current.childList.add(new Node(word.charAt(i)));
current = current.subNode(word.charAt(i));
}
current.count++;
}
// Set isEnd to indicate end of the word
current.isEnd = true;
}
public boolean search(String word) {
Node current = root;
for (int i = 0; i < word.length(); i++) {
if (current.subNode(word.charAt(i)) == null)
return false;
else
current = current.subNode(word.charAt(i));
}
/*
* This means that a string exists, but make sure its
* a word by checking its 'isEnd' flag
*/
if (current.isEnd == true) return true;
else return false;
}
public void deleteWord(String word) {
if (search(word) == false) return;
Node current = root;
for (char c : word.toCharArray()) {
Node child = current.subNode(c);
if (child.count == 1) {
current.childList.remove(child);
return;
} else {
child.count--;
current = child;
}
}
current.isEnd = false;
}
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("ball");
trie.insert("balls");
trie.insert("sense");
// testing deletion
System.out.println(trie.search("balls"));
System.out.println(trie.search("ba"));
trie.deleteWord("balls");
System.out.println(trie.search("balls"));
System.out.println(trie.search("ball"));
}
}