今天把Trie樹徹底的看了下。發現網上有兩篇非常好的文章,通過他們的博客,我對Trie樹有了大題的了解。並且通過理解 消化 綜合他們的知識,再結合我自己的編程愛好,我也把具體的程序實現了一遍,這樣能對Trie樹有更加深刻的認識!
他們是:勇幸|Thinking 和 Maik 。 感謝他們。 下面的分析也是從他們的博客摘抄以便理解的。
- Trie原理
Trie的核心思想是空間換時間。利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。
- Trie性質
好多人說trie的根節點不包含任何字符信息,我所習慣的trie根節點卻是包含信息的,而且認為這樣也方便,下面說一下它的性質
1. 字符的種數決定每個節點的出度,即branch數組(空間換時間思想)
2. branch數組的下標代表字符相對於a的相對位置
3. 采用標記的方法確定是否為字符串。
4. 插入、查找的復雜度均為O(len),len為字符串長度
其基本操作有:查找 插入和刪除(刪除全部和刪除單個單詞)
搜索字典項目的方法為:
(1) 從根結點開始一次搜索;
(2) 取得要查找關鍵詞的第一個字母,並根據該字母選擇對應的子樹並轉到該子樹繼續進行檢索;
- Trie的示意圖
如圖所示,該trie樹存有abc、d、da、dda四個字符串,如果是字符串會在節點的尾部進行標記。沒有后續字符的branch分支指向NULL

- Trie的優點舉例
已知n個由小寫字母構成的平均長度為10的單詞,判斷其中是否存在某個串為另一個串的前綴子串。下面對比3種方法:
1. 最容易想到的:即從字符串集中從頭往后搜,看每個字符串是否為字符串集中某個字符串的前綴,復雜度為O(n^2)。
2. 使用hash:我們用hash存下所有字符串的所有的前綴子串。建立存有子串hash的復雜度為O(n*len)。查詢的復雜度為O(n)* O(1)= O(n)。
3. 使用trie:因為當查詢如字符串abc是否為某個字符串的前綴時,顯然以b,c,d....等不是以a開頭的字符串就不用查找了。所以建立trie的復雜度為O(n*len),而建立+查詢在trie中是可以同時執行的,建立的過程也就可以成為查詢的過程,hash就不能實現這個功能。所以總的復雜度為O(n*len),實際查詢的復雜度只是O(len)。
Trie樹是一種非常重要的數據結構,它在信息檢索,字符串匹配等領域有廣泛的應用,同時,它也是很多算法和復雜數據結構的基礎,如后綴樹,AC自動機等,因此,掌握Trie樹這種數據結構,對於一名IT人員,顯得非常基礎且必要!(摘抄自:http://dongxicheng.org/structure/trietree/)
#include <iostream> #include <stack> using namespace std; const int sonnum=26, base='a'; struct Trie{ int num; //記錄有多少個單詞能到達次,也即相同前綴的個位 bool terminal; //判斷是否是結束節點 struct Trie *son[sonnum]; Trie() { num=1; terminal=false; memset(son,NULL,sizeof(son)); } }; Trie* NewTrie() { Trie *temp=new Trie(); return temp; } void Insert(Trie *root, char *s) { Trie *temp=root; while(*s) { if(temp->son[*s-base]==NULL) //不存在 則建立 temp->son[*s-base]=NewTrie(); else
temp->son[*s-base]->num++; temp=temp->son[*s-base]; s++; } temp->terminal=true; //到達尾部,標記一個串 } bool Search(Trie *root, char *s) { Trie *temp=root; while(*s) { if(temp->son[*s-base]!=NULL) temp=temp->son[*s-base]; else return false; s++; } return true; } void DeleteAll(Trie *root) //刪除全部節點 { Trie *temp=root; for(int i=0; i<sonnum; i++) { if(root->son[i]!=NULL) DeleteAll(root->son[i]); } delete root; } bool DeleteWord(Trie *root,char *word) //刪除某個單詞 { Trie *current=root; stack<Trie*> nodes; //用來記錄經過的中間節點,供以后自上而下的刪除 while(*word && current!=NULL) { nodes.push(current); //經過的中間節點壓棧 current=current->son[*word-base]; word++; } if(current && current->terminal) //此時current指向該word對應的最后一個節點 { while(nodes.size()!=0) { char c=*(--word); current=nodes.top()->son[c-base]; //取得當前處理的節點 if(current->num==1) //判斷該節點是否只被word用,若不是,則不能刪除 { delete current; nodes.top()->son[c-base]=NULL; //把上層的節點next中指向current節點的指針置為NULL nodes.pop(); } else //不能刪,只把num相應減1 { current->num--; nodes.pop(); while(nodes.size()!=0) { char *c=--word; current=nodes.top()->son[*c-base]; current->num--; nodes.pop(); } break; } } return true; } else return false; } int main() { Trie *root=NewTrie(); Insert(root,"a"); Insert(root,"abandon"); Insert(root,"abandoned"); //不存在的情況 if(Search(root,"abc")) printf("Found!\n"); else printf("NotFound!\n"); //存在的情況 if(Search(root,"abandon")) printf("Found!\n"); else printf("NotFound!\n"); //能找到 並刪除 if(DeleteWord(root,"abandon")) printf("Delete!\n"); else printf("NotFound\n"); //找不到 if(DeleteWord(root,"abc")) printf("Delete!\n"); else printf("NotFound!\n"); return 0; }
