Trie,又稱單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計和排序大量的字符串(但不只限於字符串),因此常常被搜索引擎系統用於文本詞頻統計。它的優勢是:最大限度地減小無謂的字符串比較,查詢效率比哈希表高。php
它有3個基本性質:html
在這個Trie結構中,保存了A、to、tea、ted、ten、i、in、inn這8個字符串,僅佔用8個字節(不包括指針佔用的空間)。數組
這是一個用於詞頻統計的程序範例,因使用了getline(3),因此須要glibc才能連接成功,沒有glibc的話能夠自行改寫。代碼由User:JohnBull捐獻,聽從GPL版權聲明。數據結構
#include <stdio.h> #include <stdlib.h> #include <string.h> #define TREE_WIDTH 256 #define WORDLENMAX 128 struct trie_node_st { int count; struct trie_node_st *next[TREE_WIDTH]; }; static struct trie_node_st root={0, {NULL}}; static char *spaces=" \t\n/.\"\'()"; static int insert(const char *word) { int i; struct trie_node_st *curr, *newnode; if (word[0]=='\0') { return 0; } curr = &root; for (i=0; ; ++i) { if (curr->next[ word[i] ] == NULL) { newnode=(struct trie_node_st*)malloc(sizeof(struct trie_node_st)); memset(newnode, 0, sizeof(struct trie_node_st)); curr->next[ word[i] ] = newnode; } if (word[i] == '\0') { break; } curr = curr->next[ word[i] ]; } curr->count ++; return 0; } static void printword(const char *str, int n) { printf("%s\t%d\n", str, n); } static int do_travel(struct trie_node_st *rootp) { static char worddump[WORDLENMAX+1]; static int pos=0; int i; if (rootp == NULL) { return 0; } if (rootp->count) { worddump[pos]='\0'; printword(worddump, rootp->count); } for (i=0;i<TREE_WIDTH;++i) { worddump[pos++]=i; do_travel(rootp->next[i]); pos--; } return 0; } int main(void) { char *linebuf=NULL, *line, *word; size_t bufsize=0; int ret; while (1) { ret=getline(&linebuf, &bufsize, stdin); if (ret==-1) { break; } line=linebuf; while (1) { word = strsep(&line, spaces); if (word==NULL) { break; } if (word[0]=='\0') { continue; } insert(word); } } /* free(linebuf); */ do_travel(&root); exit(0); }
在給一個例子:數據結構和算法
#define MAX_NUM 26 enum NODE_TYPE{ //"COMPLETED" means a string is generated so far. COMPLETED, UNCOMPLETED }; struct Node { enum NODE_TYPE type; char ch; struct Node* child[MAX_NUM]; //26-tree->a, b ,c, .....z }; struct Node* ROOT; //tree root struct Node* createNewNode(char ch){ // create a new node struct Node *new_node = (struct Node*)malloc(sizeof(struct Node)); new_node->ch = ch; new_node->type == UNCOMPLETED; int i; for(i = 0; i < MAX_NUM; i++) new_node->child[i] = NULL; return new_node; } void initialization() { //intiazation: creat an empty tree, with only a ROOT ROOT = createNewNode(' '); } int charToindex(char ch) { //a "char" maps to an index<br> return ch - 'a'; } int find(const char chars[], int len) { struct Node* ptr = ROOT; int i = 0; while(i < len) { if(ptr->child[charToindex(chars[i])] == NULL) { break; } ptr = ptr->child[charToindex(chars[i])]; i++; } return (i == len) && (ptr->type == COMPLETED); } void insert(const char chars[], int len) { struct Node* ptr = ROOT; int i; for(i = 0; i < len; i++) { if(ptr->child[charToindex(chars[i])] == NULL) { ptr->child[charToindex(chars[i])] = createNewNode(chars[i]); } ptr = ptr->child[charToindex(chars[i])]; } ptr->type = COMPLETED; }
Trie樹的基本實現ide
字母樹的插入(Insert)、刪除( Delete)和查找(Find)都很是簡單,用一個一重循環便可,即第i 次循環找到前i 個字母所對應的子樹,而後進行相應的操做。實現這棵字母樹,咱們用最多見的數組保存(靜態開闢內存)便可,固然也能夠開動態的指針類型(動態開闢內存)。至於結點對兒子的指向,通常有三種方法:
一、對每一個結點開一個字母集大小的數組,對應的下標是兒子所表示的字母,內容則是這個兒子對應在大數組上的位置,即標號;
二、對每一個結點掛一個鏈表,按必定順序記錄每一個兒子是誰;
三、使用左兒子右兄弟表示法記錄這棵樹。
三種方法,各有特色。第一種易實現,但實際的空間要求較大;第二種,較易實現,空間要求相對較小,但比較費時;第三種,空間要求最小,但相對費時且不易寫。
三、 Trie樹的高級實現
能夠採用雙數組(Double-Array)實現。利用雙數組能夠大大減少內存使用量,具體實現細節見參考資料(5)(6)。
四、 Trie樹的應用
Trie是一種很是簡單高效的數據結構,但有大量的應用實例。
(1) 字符串檢索
事先將已知的一些字符串(字典)的有關信息保存到trie樹裏,查找另一些未知字符串是否出現過或者出現頻率。
舉例:
@ 給出N 個單詞組成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最先出現的順序寫出全部不在熟詞表中的生詞。
@ 給出一個詞典,其中的單詞爲不良單詞。單詞均爲小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那麼文本problem含有不良單詞。
(2)字符串最長公共前綴
Trie樹利用多個字符串的公共前綴來節省存儲空間,反之,當咱們把大量字符串存儲到一棵trie樹上時,咱們能夠快速獲得某些字符串的公共前綴。
舉例:
@ 給出N 個小寫英文字母串,以及Q 個詢問,即詢問某兩個串的最長公共前綴的長度是多少?
解決方案:首先對全部的串創建其對應的字母樹。此時發現,對於兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,因而,問題就轉化爲了離線(Offline)的最近公共祖先(Least Common Ancestor,簡稱LCA)問題。
而最近公共祖先問題一樣是一個經典問題,能夠用下面幾種方法:
1. 利用並查集(Disjoint Set),能夠採用採用經典的Tarjan 算法;
2. 求出字母樹的歐拉序列(Euler Sequence )後,就能夠轉爲經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題了;
(關於並查集,Tarjan算法,RMQ問題,網上有不少資料。)
(3)排序
Trie樹是一棵多叉樹,只要先序遍歷整棵樹,輸出相應的字符串即是按字典序排序的結果。
舉例:
@ 給你N 個互不相同的僅由一個單詞構成的英文名,讓你將它們按字典序從小到大排序輸出。
(4) 做爲其餘數據結構和算法的輔助結構
如後綴樹,AC自動機等
五、 Trie樹複雜度分析
(1) 插入、查找的時間複雜度均爲O(N),其中N爲字符串長度。
(2) 空間複雜度是26^n級別的,很是龐大(可採用雙數組實現改善)。
六、 總結
Trie樹是一種很是重要的數據結構,它在信息檢索,字符串匹配等領域有普遍的應用,同時,它也是不少算法和複雜數據結構的基礎,如後綴樹,AC自動機等,所以,掌握Trie樹這種數據結構,對於一名IT人員,顯得很是基礎且必要!
七、 參考資料
(1)wiki:http://en.wikipedia.org/wiki/Trie
(2) 博文《字典樹的簡介及實現》:
http://hi.baidu.com/luyade1987/blog/item/2667811631106657f2de320a.html
(3) 論文《淺析字母樹在信息學競賽中的應用》
(4) 論文《Trie圖的構建、活用與改進》
(5) 博文《An Implementation of Double-Array Trie》:
http://linux.thai.net/~thep/datrie/datrie.html
(6) 論文《An Efficient Implementation of Trie Structures》:
7)剛剛分享了:"算法合集之《淺析字母樹在信息學競賽中的應用》.pdf" 下載連接:http://t.cn/zOlasAHhttp://t.cn/zOlasAQ