Trie樹(字典樹)

   很有段時間沒寫此係列了,今天我們來說Trie樹,Trie樹的名字有很多,比如字典樹,前綴樹等等。

一:概念

     下面我們有and,as,at,cn,com這些關鍵詞,那麼如何構建trie樹呢?

從上面的圖中,我們或多或少的可以發現一些好玩的特性。

      第一:根節點不包含字符,除根節點外的每一個子節點都包含一個字符。

      第二:從根節點到某一節點,路徑上經過的字符連接起來,就是該節點對應的字符串。

      第三:每個單詞的公共前綴作爲一個字符節點保存。

 

二:使用範圍

     既然學Trie樹,我們肯定要知道這玩意是用來幹嘛的。

     第一:詞頻統計。

            可能有人要說了,詞頻統計簡單啊,一個hash或者一個堆就可以打完收工,但問題來了,如果內存有限呢?還能這麼

             玩嗎?所以這裏我們就可以用trie樹來壓縮下空間,因爲公共前綴都是用一個節點保存的。

     第二: 前綴匹配

            就拿上面的圖來說吧,如果我想獲取所有以"a"開頭的字符串,從圖中可以很明顯的看到是:and,as,at,如果不用trie樹,

            你該怎麼做呢?很顯然樸素的做法時間複雜度爲O(N2) ,那麼用Trie樹就不一樣了,它可以做到h,h爲你檢索單詞的長度,

            可以說這是秒殺的效果。

舉個例子:現有一個編號爲1的字符串”and“,我們要插入到trie樹中,採用動態規劃的思想,將編號”1“計入到每個途徑的節點中,

              那麼以後我們要找”a“,”an“,”and"爲前綴的字符串的編號將會輕而易舉。

三:實際操作

     到現在爲止,我想大家已經對trie樹有了大概的掌握,下面我們看看如何來實現。

1:定義trie樹節點

     爲了方便,我也採用純英文字母,我們知道字母有26個,那麼我們構建的trie樹就是一個26叉樹,每個節點包含26個子節點。

複製代碼
 1 #region Trie樹節點
 2         /// <summary>
 3         /// Trie樹節點
 4         /// </summary>
 5         public class TrieNode
 6         {
 7             /// <summary>
 8             /// 26個字符,也就是26叉樹
 9             /// </summary>
10             public TrieNode[] childNodes;
11 
12             /// <summary>
13             /// 詞頻統計
14             /// </summary>
15             public int freq;
16 
17             /// <summary>
18             /// 記錄該節點的字符
19             /// </summary>
20             public char nodeChar;
21 
22             /// <summary>
23             /// 插入記錄時的編碼id
24             /// </summary>
25             public HashSet<int> hashSet = new HashSet<int>();
26 
27             /// <summary>
28             /// 初始化
29             /// </summary>
30             public TrieNode()
31             {
32                 childNodes = new TrieNode[26];
33                 freq = 0;
34             }
35         }
36         #endregion
複製代碼

2: 添加操作

     既然是26叉樹,那麼當前節點的後續子節點是放在當前節點的哪一叉中,也就是放在childNodes中哪一個位置,這裏我們採用

      int k = word[0] - 'a'來計算位置。

複製代碼
 1         /// <summary>
 2         /// 插入操作
 3         /// </summary>
 4         /// <param name="root"></param>
 5         /// <param name="s"></param>
 6         public void AddTrieNode(ref TrieNode root, string word, int id)
 7         {
 8             if (word.Length == 0)
 9                 return;
10 
11             //求字符地址,方便將該字符放入到26叉樹中的哪一叉中
12             int k = word[0] - 'a';
13 
14             //如果該叉樹爲空,則初始化
15             if (root.childNodes[k] == null)
16             {
17                 root.childNodes[k] = new TrieNode();
18 
19                 //記錄下字符
20                 root.childNodes[k].nodeChar = word[0];
21             }
22 
23             //該id途徑的節點
24             root.childNodes[k].hashSet.Add(id);
25 
26             var nextWord = word.Substring(1);
27 
28             //說明是最後一個字符,統計該詞出現的次數
29             if (nextWord.Length == 0)
30                 root.childNodes[k].freq++;
31 
32             AddTrieNode(ref root.childNodes[k], nextWord, id);
33         }
34         #endregion
複製代碼

3:刪除操作

     刪除操作中,我們不僅要刪除該節點的字符串編號,還要對詞頻減一操作。

複製代碼
  /// <summary>
        /// 刪除操作
        /// </summary>
        /// <param name="root"></param>
        /// <param name="newWord"></param>
        /// <param name="oldWord"></param>
        /// <param name="id"></param>
        public void DeleteTrieNode(ref TrieNode root, string word, int id)
        {
            if (word.Length == 0)
                return;

            //求字符地址,方便將該字符放入到26叉樹種的哪一顆樹中
            int k = word[0] - 'a';

            //如果該叉樹爲空,則說明沒有找到要刪除的點
            if (root.childNodes[k] == null)
                return;

            var nextWord = word.Substring(1);

            //如果是最後一個單詞,則減去詞頻
            if (word.Length == 0 && root.childNodes[k].freq > 0)
                root.childNodes[k].freq--;

            //刪除途經節點
            root.childNodes[k].hashSet.Remove(id);

            DeleteTrieNode(ref root.childNodes[k], nextWord, id);
        }
複製代碼

4:測試

   這裏我從網上下載了一套的詞彙表,共2279條詞彙,現在我們要做的就是檢索“go”開頭的詞彙,並統計go出現的頻率。

複製代碼
 1        public static void Main()
 2         {
 3             Trie trie = new Trie();
 4 
 5             var file = File.ReadAllLines(Environment.CurrentDirectory + "//1.txt");
 6 
 7             foreach (var item in file)
 8             {
 9                 var sp = item.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
10 
11                 trie.AddTrieNode(sp.LastOrDefault().ToLower(), Convert.ToInt32(sp[0]));
12             }
13 
14             Stopwatch watch = Stopwatch.StartNew();
15 
16             //檢索go開頭的字符串
17             var hashSet = trie.SearchTrie("go");
18 
19             foreach (var item in hashSet)
20             {
21                 Console.WriteLine("當前字符串的編號ID爲:{0}", item);
22             }
23 
24             watch.Stop();
25 
26             Console.WriteLine("耗費時間:{0}", watch.ElapsedMilliseconds);
27 
28             Console.WriteLine("\n\ngo 出現的次數爲:{0}\n\n", trie.WordCount("go"));
29         }
複製代碼

下面我們拿着ID到txt中去找一找,嘿嘿,是不是很有意思。

測試文件:1.txt

完整代碼:

複製代碼
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Diagnostics;
  6 using System.Threading;
  7 using System.IO;
  8 
  9 namespace ConsoleApplication2
 10 {
 11     public class Program
 12     {
 13         public static void Main()
 14         {
 15             Trie trie = new Trie();
 16 
 17             var file = File.ReadAllLines(Environment.CurrentDirectory + "//1.txt");
 18 
 19             foreach (var item in file)
 20             {
 21                 var sp = item.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
 22 
 23                 trie.AddTrieNode(sp.LastOrDefault().ToLower(), Convert.ToInt32(sp[0]));
 24             }
 25 
 26             Stopwatch watch = Stopwatch.StartNew();
 27 
 28             //檢索go開頭的字符串
 29             var hashSet = trie.SearchTrie("go");
 30 
 31             foreach (var item in hashSet)
 32             {
 33                 Console.WriteLine("當前字符串的編號ID爲:{0}", item);
 34             }
 35 
 36             watch.Stop();
 37 
 38             Console.WriteLine("耗費時間:{0}", watch.ElapsedMilliseconds);
 39 
 40             Console.WriteLine("\n\ngo 出現的次數爲:{0}\n\n", trie.WordCount("go"));
 41         }
 42     }
 43 
 44     public class Trie
 45     {
 46         public TrieNode trieNode = new TrieNode();
 47 
 48         #region Trie樹節點
 49         /// <summary>
 50         /// Trie樹節點
 51         /// </summary>
 52         public class TrieNode
 53         {
 54             /// <summary>
 55             /// 26個字符,也就是26叉樹
 56             /// </summary>
 57             public TrieNode[] childNodes;
 58 
 59             /// <summary>
 60             /// 詞頻統計
 61             /// </summary>
 62             public int freq;
 63 
 64             /// <summary>
 65             /// 記錄該節點的字符
 66             /// </summary>
 67             public char nodeChar;
 68 
 69             /// <summary>
 70             /// 插入記錄時的編號id
 71             /// </summary>
 72             public HashSet<int> hashSet = new HashSet<int>();
 73 
 74             /// <summary>
 75             /// 初始化
 76             /// </summary>
 77             public TrieNode()
 78             {
 79                 childNodes = new TrieNode[26];
 80                 freq = 0;
 81             }
 82         }
 83         #endregion
 84 
 85         #region 插入操作
 86         /// <summary>
 87         /// 插入操作
 88         /// </summary>
 89         /// <param name="word"></param>
 90         /// <param name="id"></param>
 91         public void AddTrieNode(string word, int id)
 92         {
 93             AddTrieNode(ref trieNode, word, id);
 94         }
 95 
 96         /// <summary>
 97         /// 插入操作
 98         /// </summary>
 99         /// <param name="root"></param>
100         /// <param name="s"></param>
101         public void AddTrieNode(ref TrieNode root, string word, int id)
102         {
103             if (word.Length == 0)
104                 return;
105 
106             //求字符地址,方便將該字符放入到26叉樹中的哪一叉中
107             int k = word[0] - 'a';
108 
109             //如果該叉樹爲空,則初始化
110             if (root.childNodes[k] == null)
111             {
112                 root.childNodes[k] = new TrieNode();
113 
114                 //記錄下字符
115                 root.childNodes[k].nodeChar = word[0];
116             }
117 
118             //該id途徑的節點
119             root.childNodes[k].hashSet.Add(id);
120 
121             var nextWord = word.Substring(1);
122 
123             //說明是最後一個字符,統計該詞出現的次數
124             if (nextWord.Length == 0)
125                 root.childNodes[k].freq++;
126 
127             AddTrieNode(ref root.childNodes[k], nextWord, id);
128         }
129         #endregion
130 
131         #region 檢索操作
132         /// <summary>
133         /// 檢索單詞的前綴,返回改前綴的Hash集合
134         /// </summary>
135         /// <param name="s"></param>
136         /// <returns></returns>
137         public HashSet<int> SearchTrie(string s)
138         {
139             HashSet<int> hashSet = new HashSet<int>();
140 
141             return SearchTrie(ref trieNode, s, ref hashSet);
142         }
143 
144         /// <summary>
145         /// 檢索單詞的前綴,返回改前綴的Hash集合
146         /// </summary>
147         /// <param name="root"></param>
148         /// <param name="s"></param>
149         /// <returns></returns>
150         public HashSet<int> SearchTrie(ref TrieNode root, string word, ref HashSet<int> hashSet)
151         {
152             if (word.Length == 0)
153                 return hashSet;
154 
155             int k = word[0] - 'a';
156 
157             var nextWord = word.Substring(1);
158 
159             if (nextWord.Length == 0)
160             {
161                 //採用動態規劃的思想,word最後節點記錄這途經的id
162                 hashSet = root.childNodes[k].hashSet;
163             }
164 
165             SearchTrie(ref root.childNodes[k], nextWord, ref hashSet);
166 
167             return hashSet;
168         }
169         #endregion
170 
171         #region 統計指定單詞出現的次數
172 
173         /// <summary>
174         /// 統計指定單詞出現的次數
175         /// </summary>
176         /// <param name="root"></param>
177         /// <param name="word"></param>
178         /// <returns></returns>
179         public int WordCount(string word)
180         {
181             int count = 0;
182 
183             WordCount(ref trieNode, word, ref count);
184 
185             return count;
186         }
187 
188         /// <summary>
189         /// 統計指定單詞出現的次數
190         /// </summary>
191         /// <param name="root"></param>
192         /// <param name="word"></param>
193         /// <param name="hashSet"></param>
194         /// <returns></returns>
195         public void WordCount(ref TrieNode root, string word, ref int count)
196         {
197             if (word.Length == 0)
198                 return;
199 
200             int k = word[0] - 'a';
201 
202             var nextWord = word.Substring(1);
203 
204             if (nextWord.Length == 0)
205             {
206                 //採用動態規劃的思想,word最後節點記錄這途經的id
207                 count = root.childNodes[k].freq;
208             }
209 
210             WordCount(ref root.childNodes[k], nextWord, ref count);
211         }
212 
213         #endregion
214 
215         #region 修改操作
216         /// <summary>
217         /// 修改操作
218         /// </summary>
219         /// <param name="newWord"></param>
220         /// <param name="oldWord"></param>
221         /// <param name="id"></param>
222         public void UpdateTrieNode(string newWord, string oldWord, int id)
223         {
224             UpdateTrieNode(ref trieNode, newWord, oldWord, id);
225         }
226 
227         /// <summary>
228         /// 修改操作
229         /// </summary>
230         /// <param name="root"></param>
231         /// <param name="newWord"></param>
232         /// <param name="oldWord"></param>
233         /// <param name="id"></param>
234         public void UpdateTrieNode(ref TrieNode root, string newWord, string oldWord, int id)
235         {
236             //先刪除
237             DeleteTrieNode(oldWord, id);
238 
239             //再添加
240             AddTrieNode(newWord, id);
241         }
242         #endregion
243 
244         #region 刪除操作
245         /// <summary>
246         ///  刪除操作
247         /// </summary>
248         /// <param name="root"></param>
249         /// <param name="newWord"></param>
250         /// <param name="oldWord"></param>
251         /// <param name="id"></param>
252         public void DeleteTrieNode(string word, int id)
253         {
254             DeleteTrieNode(ref trieNode, word, id);
255         }
256 
257         /// <summary>
258         /// 刪除操作
259         /// </summary>
260         /// <param name="root"></param>
261         /// <param name="newWord"></param>
262         /// <param name="oldWord"></param>
263         /// <param name="id"></param>
264         public void DeleteTrieNode(ref TrieNode root, string word, int id)
265         {
266             if (word.Length == 0)
267                 return;
268 
269             //求字符地址,方便將該字符放入到26叉樹種的哪一顆樹中
270             int k = word[0] - 'a';
271 
272             //如果該叉樹爲空,則說明沒有找到要刪除的點
273             if (root.childNodes[k] == null)
274                 return;
275 
276             var nextWord = word.Substring(1);
277 
278             //如果是最後一個單詞,則減去詞頻
279             if (word.Length == 0 && root.childNodes[k].freq > 0)
280                 root.childNodes[k].freq--;
281 
282             //刪除途經節點
283             root.childNodes[k].hashSet.Remove(id);
284 
285             DeleteTrieNode(ref root.childNodes[k], nextWord, id);
286         }
287         #endregion
288     }
289 }













1. 什麼是trie樹

  1.Trie樹 (特例結構樹)  

      Trie樹,又稱單詞查找樹、字典樹,是一種樹形結構,是一種哈希樹的變種,是一種用於快速檢索的多叉樹結構。典型應用是用於統計和排序大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。
     Trie的核心思想是空間換時間。利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。
     Trie樹也有它的缺點,Trie樹的內存消耗非常大.當然,或許用左兒子右兄弟的方法建樹的話,可能會好點.

2.  三個基本特性:  

1)根節點不包含字符,除根節點外每一個節點都只包含一個字符。  
2)從根節點到某一節點,路徑上經過的字符連接起來,爲該節點對應的字符串。 
3)每個節點的所有子節點包含的字符都不相同。

3 .例子

       和二叉查找樹不同,在trie樹中,每個結點上並非存儲一個元素。        trie樹把要查找的關鍵詞看作一個字符序列。並根據構成關鍵詞字符的先後順序構造用於檢索的樹結構。        在trie樹上進行檢索類似於查閱英語詞典。
      一棵m度的trie樹或者爲空,或者由m棵m度的trie樹構成。

     例如,電子英文詞典,爲了方便用戶快速檢索英語單詞,可以建立一棵trie樹。例如詞典由下面的單詞成:a、b、c、aa、ab、ac、ba、ca、aba、abc、baa、bab、bac、cab、abba、baba、caba、abaca、caaba

           再舉一個例子。給出一組單詞,inn, int, at, age, adv, ant, 我們可以得到下面的Trie:

        

        可以看出:

  • 每條邊對應一個字母。
  • 每個節點對應一項前綴。葉節點對應最長前綴,即單詞本身。
  • 單詞inn與單詞int有共同的前綴“in”, 因此他們共享左邊的一條分支,root->i->in。同理,ate, age, adv, 和ant共享前綴"a",所以他們共享從根節點到節點"a"的邊。

查詢操縱非常簡單。比如要查找int,順着路徑i -> in -> int就找到了。

 2. trie樹的實現

1.插入過程

對於一個單詞,從根開始,沿着單詞的各個字母所對應的樹中的節點分支向下走,直到單詞遍歷完,將最後的節點標記爲紅色,表示該單詞已插入trie樹。

2. 查找過程

其方法爲:

(1) 從根結點開始一次搜索;

(2) 取得要查找關鍵詞的第一個字母,並根據該字母選擇對應的子樹並轉到該子樹繼續進行檢索;

(3) 在相應的子樹上,取得要查找關鍵詞的第二個字母,並進一步選擇對應的子樹進行檢索。
(4) 迭代過程……
(5) 在某個結點處,關鍵詞的所有字母已被取出,則讀取附在該結點上的信息,即完成查找。其他操作類似處理.

       即從根開始按照單詞的字母順序向下遍歷trie樹,一旦發現某個節點標記不存在或者單詞遍歷完成而最後的節點未標記爲紅色,則表示該單詞不存在,若最後的節點標記爲紅色,表示該單詞存在。如下圖中:trie樹中存在的就是abc、d、da、dda四個單詞。在實際的問題中可以將標記顏色的標誌位改爲數量count等其他符合題目要求的變量。  

      

代碼:
  1. // stdafx.h : include file for standard system include files,  
  2. // or project specific include files that are used frequently, but  
  3. // are changed infrequently  
  4. //  
  5.   
  6. #pragma once  
  7.   
  8. #include <stdio.h>    
  9. #include "stdlib.h"  
  10. #include <iostream>  
  11. #include <string.h>  
  12. using namespace std;  
  13.   
  14. //宏定義      
  15. #define TRUE   1      
  16. #define FALSE   0     
  17. #define NULL 0  
  18. #define OK    1      
  19. #define ERROR   0    
  20. #define INFEASIBLE -1      
  21. #define OVERFLOW -2    
  22.   
  23. const int num_chars = 26;  
  24. class Trie {  
  25. public:  
  26.     Trie();  
  27.     Trie(Trie& tr);  
  28.     virtual ~Trie();  
  29.     int trie_search(const char* word, char* entry ) const;  
  30.     int insert(const char* word, const char* entry);  
  31.     int remove(const char* word, char* entry);  
  32. protected:  
  33.      struct Trie_node{  
  34.            char* data; //若不爲空,表示從root到此結點構成一個單詞   
  35.            Trie_node* branch[num_chars]; //分支   
  36.            Trie_node(); //構造函數   
  37.      };  
  38.        
  39.      Trie_node* root; //根結點(指針)   
  40.   
  41. };  
  1. // Test.cpp : Defines the entry point for the console application.    
  2. //    
  3. #include "stdafx.h"   
  4. Trie::Trie_node::Trie_node() {  
  5.     data = NULL;      
  6.     for (int i=0; i<num_chars; ++i)   
  7.         branch[i] = NULL;  
  8. }  
  9. Trie::Trie():root(NULL) {}  
  10. Trie::~Trie(){}  
  11. int Trie::trie_search(const char* word, char* entry ) const {    
  12.     int position = 0;  //層數   
  13.     char char_code;      
  14.   
  15.     Trie_node *location = root;  //從根結點開始   
  16.     while( location!=NULL && *word!=0 ) {       
  17.         if (*word >= 'A' && *word <= 'Z')   
  18.             char_code = *word-'A';       
  19.         else if (*word>='a' && *word<='z')   
  20.             char_code = *word-'a';       
  21.         else return 0;// 不合法的單詞     
  22.         //轉入相應分支指針   
  23.         location = location->branch[char_code];       
  24.         position++;       
  25.         word++;    
  26.     }    
  27.     //找到,獲取數據,成功返回   
  28.     if ( location != NULL && location->data != NULL ) {       
  29.         strcpy(entry,location->data);       
  30.         return 1;    
  31.     }    
  32.     else  return 0;// 不合法的單詞  
  33. }  
  34. int Trie::insert(const char* word, const char* entry) {     
  35.     int result = 1, position = 0;     
  36.     if ( root == NULL ) root = new Trie_node;   //初始插入,根結點爲空   
  37.     char char_code;     
  38.     Trie_node *location = root;   //從根結點開始   
  39.     while( location!=NULL && *word!=0 ) {         
  40.         if (*word>='A' && *word<='Z') char_code = *word-'A';         
  41.         else if (*word>='a' && *word<='z') char_code = *word-'a';         
  42.         else return 0;// 不合法的單詞      
  43.   
  44.         //不存在此分支   
  45.         if( location->branch[char_code] == NULL )              
  46.             location->branch[char_code] = new Trie_node;    //創建空分支     
  47.   
  48.         //轉入分支   
  49.         location = location->branch[char_code];         
  50.         position++;word++;   }     
  51.     if (location->data != NULL) result = 0;//欲插入的單詞已經存在      
  52.     else {    //插入數據       
  53.         location->data = new char[strlen(entry)+1];     //分配內存      
  54.         strcpy(location->data, entry);    //給data賦值表明單詞存在   
  55.     }     
  56.     return result;    
  57. }  
  58. int main(){     
  59.     Trie t;     
  60.     char entry[100];     
  61.     t.insert("a""DET");          
  62.     t.insert("abacus","NOUN");     
  63.     t.insert("abalone","NOUN");     
  64.     t.insert("abandon","VERB");     
  65.     t.insert("abandoned","ADJ");    
  66.     t.insert("abashed","ADJ");     
  67.     t.insert("abate","VERB");      
  68.     t.insert("this""PRON");     
  69.     if (t.trie_search("this", entry))        
  70.         cout<<"'this' was found. pos: "<<entry<<endl;     
  71.     if (t.trie_search("abate", entry))        
  72.         cout<<"'abate' is found. pos: "<<entry<<endl;     
  73.     if (t.trie_search("baby", entry))        
  74.         cout<<"'baby' is found. pos: "<<entry<<endl;     
  75.     else        
  76.         cout<<"'baby' does not exist at all!"<<endl;  
  77. }  

3. 查找分析

       在trie樹中查找一個關鍵字的時間和樹中包含的結點數無關,而取決於組成關鍵字的字符數。而二叉查找樹的查找時間和樹中的結點數有關O(log2n)。
       如果要查找的關鍵字可以分解成字符序列且不是很長,利用trie樹查找速度優於二叉查找樹。如:        若關鍵字長度最大是5,則利用trie樹,利用5次比較可以從26^5=11881376個可能的關鍵字中檢索出指定的關鍵字。而利用二叉查找樹至少要進行次比較。

3. trie樹的應用:

1. 字符串檢索,詞頻統計,搜索引擎的熱門查詢

        事先將已知的一些字符串(字典)的有關信息保存到trie樹裏,查找另外一些未知字符串是否出現過或者出現頻率。

        舉例:

       1)有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16字節,內存限制大小是1M。返回頻數最高的100個詞。

       2)給出N 個單詞組成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最早出現的順序寫出所有不在熟詞表中的生詞。

       3)給出一個詞典,其中的單詞爲不良單詞。單詞均爲小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那麼文本problem含有不良單詞。

       4)1000萬字符串,其中有些是重複的,需要把重複的全部去掉,保留沒有重複的字符串

       5)尋找熱門查詢:搜索引擎會通過日誌文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度爲1-255字節。假設目前有一千萬個記錄,這些查詢串的重複讀比較高,雖然總數是1千萬,但是如果去除重複和,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的用戶越多,也就越熱門。請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。

2. 字符串最長公共前綴

       Trie樹利用多個字符串的公共前綴來節省存儲空間,反之,當我們把大量字符串存儲到一棵trie樹上時,我們可以快速得到某些字符串的公共前綴。舉例:

      1) 給出N 個小寫英文字母串,以及Q 個詢問,即詢問某兩個串的最長公共前綴的長度是多少.  解決方案:

        首先對所有的串建立其對應的字母樹。此時發現,對於兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,於是,問題就轉化爲了離線  (Offline)的最近公共祖先(Least Common Ancestor,簡稱LCA)問題。

       而最近公共祖先問題同樣是一個經典問題,可以用下面幾種方法:

        1. 利用並查集(Disjoint Set),可以採用採用經典的Tarjan 算法;

        2. 求出字母樹的歐拉序列(Euler Sequence )後,就可以轉爲經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題了;

3.  排序

       Trie樹是一棵多叉樹,只要先序遍歷整棵樹,輸出相應的字符串便是按字典序排序的結果。

        舉例: 給你N 個互不相同的僅由一個單詞構成的英文名,讓你將它們按字典序從小到大排序輸出。

4 作爲其他數據結構和算法的輔助結構

       如後綴樹,AC自動機等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章