Trie,又稱字典樹、單詞查找樹,是一種樹形結構,用於保存大量的字符串,其核心思想是空間換時間。它的優點是:利用字符串的公共前綴來節約存儲空間。
相對來說,Trie樹是一種比較簡單的數據結構.理解起來比較簡單,正所謂簡單的東西也得付出代價.故Trie樹也有它的缺點,Trie樹的內存消耗非常大.當然,或許用左兒子右兄弟的方法建樹的話,可能會好點.
其基本性質可以歸納爲:
1. 根節點不包含字符,除根節點外每一個節點都只包含一個字符。
2. 從根節點到某一節點,路徑上經過的字符連接起來,爲該節點對應的字符串。
3. 每個節點的所有子節點包含的字符都不相同。
其基本操作有:查找 插入和刪除,當然刪除操作比較少見.我在這裏只是實現了對整個樹的刪除操作,至於單個word的刪除操作也很簡單.
搜索字典項目的方法爲:
(1) 從根結點開始一次搜索;
(2) 取得要查找關鍵詞的第一個字母,並根據該字母選擇對應的子樹並轉到該子樹繼續進行檢索;
(3) 在相應的子樹上,取得要查找關鍵詞的第二個字母,並進一步選擇對應的子樹進行檢索。
(4) 迭代過程……
(5) 在某個結點處,關鍵詞的所有字母已被取出,則讀取附在該結點上的信息,即完成查找。
其他操作類似處理.
舉個簡單的例子。
給你100000個長度不超過10的單詞。對於每一個單詞,我們要判斷他出沒出現過,如果出現了,第一次出現第幾個位置。
這題當然可以用hash來,但是我要介紹的是trie樹。在某些方面它的用途更大。比如說對於某一個單詞,我要詢問它的前綴是否出現過。這樣hash就不好搞了,而用trie還是很簡單。
現在回到例子中,如果我們用最傻的方法,對於每一個單詞,我們都要去查找它前面的單詞中是否有它。那麼這個算法的複雜度就是O(n^2)。顯然對於100000的範圍難以接受。現在我們換個思路想。假設我要查詢的單詞是abcd,那麼在他前面的單詞中,以b,c,d,f之類開頭的我顯然不必考慮。而只要找以a開頭的中是否存在abcd就可以了。同樣的,在以a開頭中的單詞中,我們只要考慮以b作爲第二個字母的……這樣一個樹的模型就漸漸清晰了……
假設有b,abc,abd,bcd,abcd,efg,hii這6個單詞,我們構建的樹就是這樣的
對於每一個節點,從根遍歷到他的過程就是一個單詞,如果這個節點被標記爲紅色,就表示這個單詞存在,否則不存在。
那麼,對於一個單詞,我只要順着他從跟走到對應的節點,再看這個節點是否被標記爲紅色就可以知道它是否出現過了。把這個節點標記爲紅色,就相當於插入了這個單詞。
這樣一來我們詢問和插入可以一起完成,所用時間僅僅爲單詞長度,在這一個樣例,便是10。
我們可以看到,trie樹每一層的節點數是26^i級別的。所以爲了節省空間。我們用動態鏈表,或者用數組來模擬動態。空間的花費,不會超過單詞數×單詞長度。
給出一個用類封裝的字典樹代碼
#include <cstdio> #include <iostream> #include <cstring> using namespace std;
const int num_chars = 26;
class Trie { public: Trie():root(NULL){}; Trie(Trie& tr);
int search(const char* word, char* entry ) const; int insert(const char* word, const char* entry); int remove(const char* word, char* entry); private: struct Trie_node { char* data; Trie_node* branch[num_chars]; Trie_node(); }* root; }; Trie::Trie_node::Trie_node() { data = NULL; for (int i=0; i<num_chars; ++i) branch[i] = NULL; }
int Trie::search(const char* word, char* entry ) const { int position = 0; char char_code; Trie_node *location = root; while( location!=NULL && *word!=0 ) { if (*word>='A' && *word<='Z') char_code = *word-'A'; else if (*word>='a' && *word<='z') char_code = *word-'a'; else return 0;
location = location->branch[char_code]; position++; word++; } if ( location != NULL && location->data != NULL ) { strcpy(entry,location->data); return 1; } else return 0; } int Trie::insert(const char* word, const char* entry) { int result = 1, position = 0; if ( root == NULL ) root = new Trie_node; char char_code; Trie_node *location = root; while( location!=NULL && *word!=0 ) { if (*word>='A' && *word<='Z') char_code = *word-'A'; else if (*word>='a' && *word<='z') char_code = *word-'a'; else return 0;
if( location->branch[char_code] == NULL ) location->branch[char_code] = new Trie_node;
location = location->branch[char_code]; position++; word++; } if (location->data != NULL) result = 0; else { location->data = new char[strlen(entry)+1]; strcpy(location->data, entry); } return result; } int main() { Trie t; char entry[100]; t.insert("aa", "DET"); t.insert("abacus","NOUN"); t.insert("abalone","NOUN"); t.insert("abandon","VERB"); t.insert("abandoned","ADJ"); t.insert("abashed","ADJ"); t.insert("abate","VERB"); t.insert("this", "PRON"); if (t.search("this", entry)) cout<<"'this' was found. pos: "<<entry<<endl; if (t.search("abate", entry)) cout<<"'abate' is found. pos: "<<entry<<endl; if (t.search("baby", entry)) cout<<"'baby' is found. pos: "<<entry<<endl; else cout<<"'baby' does not exist at all!"<<endl; if (t.search("aa", entry)) cout<<"'aa was found. pos: "<<entry<<endl; }
|