摘要:本文藉助遞歸方式,用盡可能短的代碼實現了Trie,重在討論Trie的結構和思想,此方式實現的Trie效率較低,不能應用於實際工程中。
Trie的應用
trie: 發音爲try,是一種應用於字符串查找的特殊查找表,首先介紹一下他的優點以及適用場景吧,實際上我們對於可能天天都在享受這項技術帶來的便利,但卻渾然不知,我貼上圖,相信您就能立馬明白:
該數據結構能夠在常數級別判斷數據庫中所有以目標字符串爲前綴的字符串。假如我們是用哈希表來存儲我們的詞表,顯然是做不到的。
Trie的數據結構
總結來講,trie是一種利用空間換時間的算法思想,對於每個節點,存儲的是所有字符表的字符,用從根節點到葉節點的路徑來指示存儲內容,圖形化表示如下:
可以看出,假如Trie存儲的內容不多的話,有大量的節點被浪費掉了,存儲密度很低,十分浪費內存,當詞表閱讀,存儲的單詞越少,浪費的內存就越高。
Trie的實現
首選定義節點結構,採用遞歸定義的方式:
class Trie{
private Trie[] array;
//假設詞表中僅存儲小寫單詞
private static final int N = 26;
private boolean isEnd;
public Trie() {
array = new Trie[N];
isEnd = false;
}
}
//
首先來看查詢代碼,這裏爲了方便,我們採用遞歸的方式,我們假設當查詢到單詞結尾時,該路徑不爲空,那麼 那麼此時存在該單詞,但是這樣無法區分重疊路徑的情況,假設Trie中存在apple這個單詞,但是不存在app這個單詞,儘管我們查詢到最後,p指向的路徑不爲空,但是其實不存在這個單詞,因此我們需要對這種情況加以區分。我們引入isEnd這個標誌,把路徑的結尾加上isEnd標誌,這樣就可以將這兩種情況區分開來了。解決了遞歸出口問題,那麼遞歸也很容易寫了直接返回array[s.charAt(0)-'a'].search(s.substring(1))即可。代碼如下,非常簡單:
public boolean search(String word) {
if(word.length() == 0) return isEnd;
int idx = word.charAt(0) - 'a';
if(array[idx] == null) return false;
else return array[idx].search(word.substring(1));
}
既然能夠寫出search代碼,那麼很顯然startWith這個函數也很容易實現,因爲只要存在這條路徑即可,不需要判斷是否是葉節點,代碼如下:
public boolean startsWith(String word) {
if(word.length() == 0) return true;
int idx = word.charAt(0) - 'a';
if(array[idx] == null) return false;
return array[idx].startsWith(word.substring(1));
}
同理,我們採用遞歸的方式插入代碼,由於我們規定不準插入空字符串,那麼遞歸出口便是當單詞長度爲1時,先看代碼:
public void insert(String word) {
if(word.length() == 0) return;
int idx = word.charAt(0) - 'a';
if(array[idx] == null) array[idx] = new Trie();
if(word.length() != 1) {
array[idx].insert(word.substring(1));
}
else array[idx].isEnd = true;
}