字典樹(trie樹)
字典樹是一種在字符串查找,前綴匹配等方面應用廣泛的算法,它在查找字符串時只與被查詢的字符串長度有關,所以它在查找時只有O(1)的時間復雜度,但隨之而來的較大的空間復雜度。
一.原理分析
如圖,字典樹的每一個節點是由一個數據域(用來標記是否在此處有字符串終止)與26個指針域(表示26個小寫字母)組成(PS:聯想鏈表)。每個節點表示一個字符,我們將我們將要輸入的字符串插入字典樹中,從根節點到某一節點(具有終止標記,上圖紅點),為已插入字符串,上圖中的字符串有:abc、abcd、abd、b、bcd、efg、hig(圖片轉載字百度圖片)。
二.分步實現
1.trie樹結構體定義
1 struct node 2 { 3 bool k; 4 node *next[26]; //定義的字典樹為只有26個小寫字母,可增加減少
5 node() 6 { 7 int i; 8 for(i=0; i<26; i++) 9 next[i] = NULL; 10 k = false; 11 } 12 ~node() 13 { 14 int i; 15 for(i=0;i<26;i++) 16 { 17 if(next[i]!=NULL) 18 { 19 delete next[i]; 20 } 21 } 22 } 23 }; 24 node *head;
PS:被注釋掉部分是用構造函數初始化節點與使用析構函數刪除節點。
上圖代碼中的next[26]表示每個節點的下一層節點數量,小寫字母為26,加上大寫就是52,加上數字就是62,以此類推。而bool型的K則表示該位置是否有終止標記,可根據題意改變為其他標記。
2.字典樹的插入
1 void insert_ch(char *ch) 2 { 3 int i; 4 node *p=head; 5 for(i=0; ch[i]; i++) 6 { 7 if(p->next[ch[i]-'a'] == NULL) //判斷下層節點是否存在 8 p->next[ch[i]-'a'] = new node; //開辟新空間 9 p = p->next[ch[i]-'a']; //向下一層進行拓展 10 } 11 p -> k = true; //進行字符串結尾標記 12 }
每次從根節點進行插入,如果向下的節點已經存在,就直接讀取,否則拓展一個新節點。之后將最后一個節點的k標記為true表示該位置有一個字符串結尾。
3.字符串查找
1 bool find_ch(char *ch) 2 { 3 int i; 4 node *p=head; 5 for(i=0; ch[i]; i++) 6 { 7 if(p->next[ch[i]-'a']==NULL) 8 return false; 9 p = p -> next[ch[i]-'a']; 10 } 11 return p -> k; 12 }
基本過程與插入相同,向下查找,入過該節點不存在,直接返回false,如果存在一直向下查找,最終返回末尾標記的k。
1.模板代碼
1 #include<algorithm> 2 #include<iostream> 3 #include<cstdio> 4 #include<cmath> 5 #include<cstring> 6 using namespace std; 7 struct node 8 { 9 bool k; 10 node *next[26]; 11 node() 12 { 13 int i; 14 for(i=0;i<26;i++) 15 { 16 next[i]=NULL; 17 k=false; 18 } 19 } 20 ~node() 21 { 22 int i; 23 for(i=0;i<26;i++) 24 { 25 if(next[i] != NULL) 26 delete next[i]; 27 } 28 29 } 30 }; 31 node *head; 32 void insert_ch(char *ch) 33 { 34 int i; 35 node *p = head; 36 for(i=0;ch[i];i++) 37 { 38 if(p -> next[ch[i]-'a' ] == NULL) 39 p -> next[ch[i]-'a' ] = new node; 40 p = p -> next[ch[i]-'a' ]; 41 } 42 p -> k = true; 43 } 44 bool find_ch(char *ch) 45 { 46 int i; 47 node *p = head; 48 for(i=0;ch[i];i++) 49 { 50 if(p -> next[ch[i]-'a' ] == NULL) 51 return false; 52 p = p -> next[ch[i]-'a' ]; 53 } 54 return p -> k; 55 } 56 char ch[100]; 57 int main() 58 { 59 head = new node; 60 while(~scanf("%s",ch)) 61 insert_ch(ch); 62 while(~scanf("%s",ch)) 63 printf("%s\n",find_ch(ch) ? "YES" : "NO"); 64 delete head; //刪除節點 65 return 0; 66 }
三.另一種實現方式
動態分配內存對空間掌控很好,用一個節點開辟一個節點,但是在做題中有時會出現大大小小的問題不好結決,所以可以選擇另一種方法,直接開辟出一個很大的結構體數組用來保存節點,不用考慮開辟與刪除節點問題。
1 #include<algorithm> 2 #include<iostream> 3 #include<cstdio> 4 #include<cmath> 5 #include<cstring> 6 using namespace std; 7 struct node 8 { 9 bool k; 10 node *next[26]; 11 }; 12 node no[10000000],*head; 13 int iii; 14 node *new_node() 15 { 16 int i; 17 node *p=&no[iii++]; 18 for(i=0;i<26;i++) 19 { 20 p -> next[i] = NULL; 21 } 22 p -> k = false; 23 } 24 void insert_ch(char *ch) 25 { 26 int i; 27 node *p=head,*t; 28 for(i=0; ch[i] ;i++ ) 29 { 30 if(p->next[ch[i]-'a']==NULL) 31 { 32 t=new_node(); 33 p->next[ch[i]-'a']=t; 34 } 35 p = p->next[ch[i]-'a']; 36 } 37 p -> k = true; 38 } 39 bool find_ch(char ch[]) 40 { 41 int i; 42 node *p=head; 43 for(i=0;i<ch[i];i++) 44 { 45 if(p->next[ch[i]-'a']==NULL) 46 return false; 47 p = p -> next[ch[i]-'a']; 48 } 49 return p -> k; 50 } 51 int main() 52 { 53 char s[100]; 54 int i,n; 55 head = new_node(); 56 while(~scanf("%d",&n)) 57 { 58 iii=0; 59 for(i=0;i<n;i++) 60 { 61 scanf("%s",s); 62 insert_ch(s); 63 } 64 scanf("%d",&n); 65 for(i=0;i<n;i++) 66 { 67 scanf("%s",s); 68 printf("%s\n",find_ch(s) ? "YES" : "NO"); 69 } 70 } 71 return 0; 72 }
這種方法與上面的基本相同,只是節點不在用new開辟,而是直接從結構體數組中取用,這種方法的弊端節點有最大值,當需要保存的數據量過大時,會數組越界。保存數據量小時對空間的浪費也很嚴重。
四.相關基礎題目
HDOJ 1251 統計難題:
http://acm.hdu.edu.cn/showproblem.php?pid=1251
HDOJ 1671 Phone List:
http://acm.hdu.edu.cn/showproblem.php?pid=1671 //用第一種方法記得釋放空間,否則超內存
HDOJ 1247 Hat’s Words:
http://acm.hdu.edu.cn/showproblem.php?pid=1247
PS:本貼純手打,如打碼有問題請各位大牛指正,謝謝大家~
歡迎大家轉載哦~請於此處注明出處:http://www.cnblogs.com/zhuyuan/