Trie树(字典树)

阳光穿透心脏的1/2处 2022-08-14 00:00 380阅读 0赞

很有段时间没写此系列了,今天我们来说Trie树,Trie树的名字有很多,比如字典树,前缀树等等。

一:概念

下面我们有and,as,at,cn,com这些关键词,那么如何构建trie树呢?

2012112521092438.png

从上面的图中,我们或多或少的可以发现一些好玩的特性。

第一:根节点不包含字符,除根节点外的每一个子节点都包含一个字符。

第二:从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串。

第三:每个单词的公共前缀作为一个字符节点保存。

二:使用范围

既然学Trie树,我们肯定要知道这玩意是用来干嘛的。

第一:词频统计。

可能有人要说了,词频统计简单啊,一个hash或者一个堆就可以打完收工,但问题来了,如果内存有限呢?还能这么

玩吗?所以这里我们就可以用trie树来压缩下空间,因为公共前缀都是用一个节点保存的。

第二: 前缀匹配

就拿上面的图来说吧,如果我想获取所有以”a”开头的字符串,从图中可以很明显的看到是:and,as,at,如果不用trie树,

你该怎么做呢?很显然朴素的做法时间复杂度为O(N2) ,那么用Trie树就不一样了,它可以做到h,h为你检索单词的长度,

可以说这是秒杀的效果。

举个例子:现有一个编号为1的字符串”and“,我们要插入到trie树中,采用动态规划的思想,将编号”1“计入到每个途径的节点中,

那么以后我们要找”a“,”an“,”and”为前缀的字符串的编号将会轻而易举。

2012112521371883.png

三:实际操作

到现在为止,我想大家已经对trie树有了大概的掌握,下面我们看看如何来实现。

1:定义trie树节点

为了方便,我也采用纯英文字母,我们知道字母有26个,那么我们构建的trie树就是一个26叉树,每个节点包含26个子节点。

复制代码

  1. 1 #region Trie树节点
  2. 2 /// <summary>
  3. 3 /// Trie树节点
  4. 4 /// </summary>
  5. 5 public class TrieNode
  6. 6 {
  7. 7 /// <summary>
  8. 8 /// 26个字符,也就是26叉树
  9. 9 /// </summary>
  10. 10 public TrieNode[] childNodes;
  11. 11
  12. 12 /// <summary>
  13. 13 /// 词频统计
  14. 14 /// </summary>
  15. 15 public int freq;
  16. 16
  17. 17 /// <summary>
  18. 18 /// 记录该节点的字符
  19. 19 /// </summary>
  20. 20 public char nodeChar;
  21. 21
  22. 22 /// <summary>
  23. 23 /// 插入记录时的编码id
  24. 24 /// </summary>
  25. 25 public HashSet<int> hashSet = new HashSet<int>();
  26. 26
  27. 27 /// <summary>
  28. 28 /// 初始化
  29. 29 /// </summary>
  30. 30 public TrieNode()
  31. 31 {
  32. 32 childNodes = new TrieNode[26];
  33. 33 freq = 0;
  34. 34 }
  35. 35 }
  36. 36 #endregion

复制代码

2: 添加操作

既然是26叉树,那么当前节点的后续子节点是放在当前节点的哪一叉中,也就是放在childNodes中哪一个位置,这里我们采用

int k = word[0] - ‘a’来计算位置。

复制代码

  1. 1 /// <summary>
  2. 2 /// 插入操作
  3. 3 /// </summary>
  4. 4 /// <param name="root"></param>
  5. 5 /// <param name="s"></param>
  6. 6 public void AddTrieNode(ref TrieNode root, string word, int id)
  7. 7 {
  8. 8 if (word.Length == 0)
  9. 9 return;
  10. 10
  11. 11 //求字符地址,方便将该字符放入到26叉树中的哪一叉中
  12. 12 int k = word[0] - 'a';
  13. 13
  14. 14 //如果该叉树为空,则初始化
  15. 15 if (root.childNodes[k] == null)
  16. 16 {
  17. 17 root.childNodes[k] = new TrieNode();
  18. 18
  19. 19 //记录下字符
  20. 20 root.childNodes[k].nodeChar = word[0];
  21. 21 }
  22. 22
  23. 23 //该id途径的节点
  24. 24 root.childNodes[k].hashSet.Add(id);
  25. 25
  26. 26 var nextWord = word.Substring(1);
  27. 27
  28. 28 //说明是最后一个字符,统计该词出现的次数
  29. 29 if (nextWord.Length == 0)
  30. 30 root.childNodes[k].freq++;
  31. 31
  32. 32 AddTrieNode(ref root.childNodes[k], nextWord, id);
  33. 33 }
  34. 34 #endregion

复制代码

3:删除操作

删除操作中,我们不仅要删除该节点的字符串编号,还要对词频减一操作。

复制代码

  1. /// <summary>
  2. /// 删除操作
  3. /// </summary>
  4. /// <param name="root"></param>
  5. /// <param name="newWord"></param>
  6. /// <param name="oldWord"></param>
  7. /// <param name="id"></param>
  8. public void DeleteTrieNode(ref TrieNode root, string word, int id)
  9. {
  10. if (word.Length == 0)
  11. return;
  12. //求字符地址,方便将该字符放入到26叉树种的哪一颗树中
  13. int k = word[0] - 'a';
  14. //如果该叉树为空,则说明没有找到要删除的点
  15. if (root.childNodes[k] == null)
  16. return;
  17. var nextWord = word.Substring(1);
  18. //如果是最后一个单词,则减去词频
  19. if (word.Length == 0 && root.childNodes[k].freq > 0)
  20. root.childNodes[k].freq--;
  21. //删除途经节点
  22. root.childNodes[k].hashSet.Remove(id);
  23. DeleteTrieNode(ref root.childNodes[k], nextWord, id);
  24. }

复制代码

4:测试

这里我从网上下载了一套的词汇表,共2279条词汇,现在我们要做的就是检索“go”开头的词汇,并统计go出现的频率。

复制代码

  1. 1 public static void Main()
  2. 2 {
  3. 3 Trie trie = new Trie();
  4. 4
  5. 5 var file = File.ReadAllLines(Environment.CurrentDirectory + "//1.txt");
  6. 6
  7. 7 foreach (var item in file)
  8. 8 {
  9. 9 var sp = item.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  10. 10
  11. 11 trie.AddTrieNode(sp.LastOrDefault().ToLower(), Convert.ToInt32(sp[0]));
  12. 12 }
  13. 13
  14. 14 Stopwatch watch = Stopwatch.StartNew();
  15. 15
  16. 16 //检索go开头的字符串
  17. 17 var hashSet = trie.SearchTrie("go");
  18. 18
  19. 19 foreach (var item in hashSet)
  20. 20 {
  21. 21 Console.WriteLine("当前字符串的编号ID为:{0}", item);
  22. 22 }
  23. 23
  24. 24 watch.Stop();
  25. 25
  26. 26 Console.WriteLine("耗费时间:{0}", watch.ElapsedMilliseconds);
  27. 27
  28. 28 Console.WriteLine("\n\ngo 出现的次数为:{0}\n\n", trie.WordCount("go"));
  29. 29 }

复制代码

2012112522045926.png

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

2012112522115572.png

测试文件:1.txt

完整代码:

ExpandedBlockStart.gif

复制代码

  1. 1 using System;
  2. 2 using System.Collections.Generic;
  3. 3 using System.Linq;
  4. 4 using System.Text;
  5. 5 using System.Diagnostics;
  6. 6 using System.Threading;
  7. 7 using System.IO;
  8. 8
  9. 9 namespace ConsoleApplication2
  10. 10 {
  11. 11 public class Program
  12. 12 {
  13. 13 public static void Main()
  14. 14 {
  15. 15 Trie trie = new Trie();
  16. 16
  17. 17 var file = File.ReadAllLines(Environment.CurrentDirectory + "//1.txt");
  18. 18
  19. 19 foreach (var item in file)
  20. 20 {
  21. 21 var sp = item.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  22. 22
  23. 23 trie.AddTrieNode(sp.LastOrDefault().ToLower(), Convert.ToInt32(sp[0]));
  24. 24 }
  25. 25
  26. 26 Stopwatch watch = Stopwatch.StartNew();
  27. 27
  28. 28 //检索go开头的字符串
  29. 29 var hashSet = trie.SearchTrie("go");
  30. 30
  31. 31 foreach (var item in hashSet)
  32. 32 {
  33. 33 Console.WriteLine("当前字符串的编号ID为:{0}", item);
  34. 34 }
  35. 35
  36. 36 watch.Stop();
  37. 37
  38. 38 Console.WriteLine("耗费时间:{0}", watch.ElapsedMilliseconds);
  39. 39
  40. 40 Console.WriteLine("\n\ngo 出现的次数为:{0}\n\n", trie.WordCount("go"));
  41. 41 }
  42. 42 }
  43. 43
  44. 44 public class Trie
  45. 45 {
  46. 46 public TrieNode trieNode = new TrieNode();
  47. 47
  48. 48 #region Trie树节点
  49. 49 /// <summary>
  50. 50 /// Trie树节点
  51. 51 /// </summary>
  52. 52 public class TrieNode
  53. 53 {
  54. 54 /// <summary>
  55. 55 /// 26个字符,也就是26叉树
  56. 56 /// </summary>
  57. 57 public TrieNode[] childNodes;
  58. 58
  59. 59 /// <summary>
  60. 60 /// 词频统计
  61. 61 /// </summary>
  62. 62 public int freq;
  63. 63
  64. 64 /// <summary>
  65. 65 /// 记录该节点的字符
  66. 66 /// </summary>
  67. 67 public char nodeChar;
  68. 68
  69. 69 /// <summary>
  70. 70 /// 插入记录时的编号id
  71. 71 /// </summary>
  72. 72 public HashSet<int> hashSet = new HashSet<int>();
  73. 73
  74. 74 /// <summary>
  75. 75 /// 初始化
  76. 76 /// </summary>
  77. 77 public TrieNode()
  78. 78 {
  79. 79 childNodes = new TrieNode[26];
  80. 80 freq = 0;
  81. 81 }
  82. 82 }
  83. 83 #endregion
  84. 84
  85. 85 #region 插入操作
  86. 86 /// <summary>
  87. 87 /// 插入操作
  88. 88 /// </summary>
  89. 89 /// <param name="word"></param>
  90. 90 /// <param name="id"></param>
  91. 91 public void AddTrieNode(string word, int id)
  92. 92 {
  93. 93 AddTrieNode(ref trieNode, word, id);
  94. 94 }
  95. 95
  96. 96 /// <summary>
  97. 97 /// 插入操作
  98. 98 /// </summary>
  99. 99 /// <param name="root"></param>
  100. 100 /// <param name="s"></param>
  101. 101 public void AddTrieNode(ref TrieNode root, string word, int id)
  102. 102 {
  103. 103 if (word.Length == 0)
  104. 104 return;
  105. 105
  106. 106 //求字符地址,方便将该字符放入到26叉树中的哪一叉中
  107. 107 int k = word[0] - 'a';
  108. 108
  109. 109 //如果该叉树为空,则初始化
  110. 110 if (root.childNodes[k] == null)
  111. 111 {
  112. 112 root.childNodes[k] = new TrieNode();
  113. 113
  114. 114 //记录下字符
  115. 115 root.childNodes[k].nodeChar = word[0];
  116. 116 }
  117. 117
  118. 118 //该id途径的节点
  119. 119 root.childNodes[k].hashSet.Add(id);
  120. 120
  121. 121 var nextWord = word.Substring(1);
  122. 122
  123. 123 //说明是最后一个字符,统计该词出现的次数
  124. 124 if (nextWord.Length == 0)
  125. 125 root.childNodes[k].freq++;
  126. 126
  127. 127 AddTrieNode(ref root.childNodes[k], nextWord, id);
  128. 128 }
  129. 129 #endregion
  130. 130
  131. 131 #region 检索操作
  132. 132 /// <summary>
  133. 133 /// 检索单词的前缀,返回改前缀的Hash集合
  134. 134 /// </summary>
  135. 135 /// <param name="s"></param>
  136. 136 /// <returns></returns>
  137. 137 public HashSet<int> SearchTrie(string s)
  138. 138 {
  139. 139 HashSet<int> hashSet = new HashSet<int>();
  140. 140
  141. 141 return SearchTrie(ref trieNode, s, ref hashSet);
  142. 142 }
  143. 143
  144. 144 /// <summary>
  145. 145 /// 检索单词的前缀,返回改前缀的Hash集合
  146. 146 /// </summary>
  147. 147 /// <param name="root"></param>
  148. 148 /// <param name="s"></param>
  149. 149 /// <returns></returns>
  150. 150 public HashSet<int> SearchTrie(ref TrieNode root, string word, ref HashSet<int> hashSet)
  151. 151 {
  152. 152 if (word.Length == 0)
  153. 153 return hashSet;
  154. 154
  155. 155 int k = word[0] - 'a';
  156. 156
  157. 157 var nextWord = word.Substring(1);
  158. 158
  159. 159 if (nextWord.Length == 0)
  160. 160 {
  161. 161 //采用动态规划的思想,word最后节点记录这途经的id
  162. 162 hashSet = root.childNodes[k].hashSet;
  163. 163 }
  164. 164
  165. 165 SearchTrie(ref root.childNodes[k], nextWord, ref hashSet);
  166. 166
  167. 167 return hashSet;
  168. 168 }
  169. 169 #endregion
  170. 170
  171. 171 #region 统计指定单词出现的次数
  172. 172
  173. 173 /// <summary>
  174. 174 /// 统计指定单词出现的次数
  175. 175 /// </summary>
  176. 176 /// <param name="root"></param>
  177. 177 /// <param name="word"></param>
  178. 178 /// <returns></returns>
  179. 179 public int WordCount(string word)
  180. 180 {
  181. 181 int count = 0;
  182. 182
  183. 183 WordCount(ref trieNode, word, ref count);
  184. 184
  185. 185 return count;
  186. 186 }
  187. 187
  188. 188 /// <summary>
  189. 189 /// 统计指定单词出现的次数
  190. 190 /// </summary>
  191. 191 /// <param name="root"></param>
  192. 192 /// <param name="word"></param>
  193. 193 /// <param name="hashSet"></param>
  194. 194 /// <returns></returns>
  195. 195 public void WordCount(ref TrieNode root, string word, ref int count)
  196. 196 {
  197. 197 if (word.Length == 0)
  198. 198 return;
  199. 199
  200. 200 int k = word[0] - 'a';
  201. 201
  202. 202 var nextWord = word.Substring(1);
  203. 203
  204. 204 if (nextWord.Length == 0)
  205. 205 {
  206. 206 //采用动态规划的思想,word最后节点记录这途经的id
  207. 207 count = root.childNodes[k].freq;
  208. 208 }
  209. 209
  210. 210 WordCount(ref root.childNodes[k], nextWord, ref count);
  211. 211 }
  212. 212
  213. 213 #endregion
  214. 214
  215. 215 #region 修改操作
  216. 216 /// <summary>
  217. 217 /// 修改操作
  218. 218 /// </summary>
  219. 219 /// <param name="newWord"></param>
  220. 220 /// <param name="oldWord"></param>
  221. 221 /// <param name="id"></param>
  222. 222 public void UpdateTrieNode(string newWord, string oldWord, int id)
  223. 223 {
  224. 224 UpdateTrieNode(ref trieNode, newWord, oldWord, id);
  225. 225 }
  226. 226
  227. 227 /// <summary>
  228. 228 /// 修改操作
  229. 229 /// </summary>
  230. 230 /// <param name="root"></param>
  231. 231 /// <param name="newWord"></param>
  232. 232 /// <param name="oldWord"></param>
  233. 233 /// <param name="id"></param>
  234. 234 public void UpdateTrieNode(ref TrieNode root, string newWord, string oldWord, int id)
  235. 235 {
  236. 236 //先删除
  237. 237 DeleteTrieNode(oldWord, id);
  238. 238
  239. 239 //再添加
  240. 240 AddTrieNode(newWord, id);
  241. 241 }
  242. 242 #endregion
  243. 243
  244. 244 #region 删除操作
  245. 245 /// <summary>
  246. 246 /// 删除操作
  247. 247 /// </summary>
  248. 248 /// <param name="root"></param>
  249. 249 /// <param name="newWord"></param>
  250. 250 /// <param name="oldWord"></param>
  251. 251 /// <param name="id"></param>
  252. 252 public void DeleteTrieNode(string word, int id)
  253. 253 {
  254. 254 DeleteTrieNode(ref trieNode, word, id);
  255. 255 }
  256. 256
  257. 257 /// <summary>
  258. 258 /// 删除操作
  259. 259 /// </summary>
  260. 260 /// <param name="root"></param>
  261. 261 /// <param name="newWord"></param>
  262. 262 /// <param name="oldWord"></param>
  263. 263 /// <param name="id"></param>
  264. 264 public void DeleteTrieNode(ref TrieNode root, string word, int id)
  265. 265 {
  266. 266 if (word.Length == 0)
  267. 267 return;
  268. 268
  269. 269 //求字符地址,方便将该字符放入到26叉树种的哪一颗树中
  270. 270 int k = word[0] - 'a';
  271. 271
  272. 272 //如果该叉树为空,则说明没有找到要删除的点
  273. 273 if (root.childNodes[k] == null)
  274. 274 return;
  275. 275
  276. 276 var nextWord = word.Substring(1);
  277. 277
  278. 278 //如果是最后一个单词,则减去词频
  279. 279 if (word.Length == 0 && root.childNodes[k].freq > 0)
  280. 280 root.childNodes[k].freq--;
  281. 281
  282. 282 //删除途经节点
  283. 283 root.childNodes[k].hashSet.Remove(id);
  284. 284
  285. 285 DeleteTrieNode(ref root.childNodes[k], nextWord, id);
  286. 286 }
  287. 287 #endregion
  288. 288 }
  289. 289 }
  290. 1. 什么是trie 1.Trie (特例结构树)
  291. Trie树,又称单词查找树、字典树,是一种树形结构,是一种哈希树的变种,是一种用于快速检索的多叉树结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
  292. Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
  293. Trie树也有它的缺点,Trie树的内存消耗非常大.当然,或许用左儿子右兄弟的方法建树的话,可能会好点.
  294. 2. 三个基本特性:  
  295. 1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。  
  296. 2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。 
  297. 3)每个节点的所有子节点包含的字符都不相同。
  298. 3 .例子
  299. 和二叉查找树不同,在trie树中,每个结点上并非存储一个元素。
  300. trie树把要查找的关键词看作一个字符序列。并根据构成关键词字符的先后顺序构造用于检索的树结构。
  301. trie树上进行检索类似于查阅英语词典。
  302. 一棵m度的trie树或者为空,或者由mm度的trie树构成。
  303. 例如,电子英文词典,为了方便用户快速检索英语单词,可以建立一棵trie树。例如词典由下面的单词成:abcaaabacbacaabaabcbaababbaccababbababacabaabacacaaba
  304. 再举一个例子。给出一组单词,inn, int, at, age, adv, ant, 我们可以得到下面的Trie
  305. 可以看出:
  306. 每条边对应一个字母。
  307. 每个节点对应一项前缀。叶节点对应最长前缀,即单词本身。
  308. 单词inn与单词int有共同的前缀“in”, 因此他们共享左边的一条分支,root->i->in。同理,ate, age, adv, ant共享前缀"a",所以他们共享从根节点到节点"a"的边。
  309. 查询操纵非常简单。比如要查找int,顺着路径i -> in -> int就找到了。
  310. 2. trie树的实现
  311. 1.插入过程
  312. 对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入trie树。
  313. 2. 查找过程
  314. 其方法为:
  315. (1) 从根结点开始一次搜索;
  316. (2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
  317. (3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
  318. (4) 迭代过程……
  319. (5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。其他操作类似处理.
  320. 即从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。如下图中:trie树中存在的就是abcddadda四个单词。在实际的问题中可以将标记颜色的标志位改为数量count等其他符合题目要求的变量。
  321. 代码:
  322. // stdafx.h : include file for standard system include files,
  323. // or project specific include files that are used frequently, but
  324. // are changed infrequently
  325. //
  326. #pragma once
  327. #include <stdio.h>
  328. #include "stdlib.h"
  329. #include <iostream>
  330. #include <string.h>
  331. using namespace std;
  332. //宏定义
  333. #define TRUE 1
  334. #define FALSE 0
  335. #define NULL 0
  336. #define OK 1
  337. #define ERROR 0
  338. #define INFEASIBLE -1
  339. #define OVERFLOW -2
  340. const int num_chars = 26;
  341. class Trie {
  342. public:
  343. Trie();
  344. Trie(Trie& tr);
  345. virtual ~Trie();
  346. int trie_search(const char* word, char* entry ) const;
  347. int insert(const char* word, const char* entry);
  348. int remove(const char* word, char* entry);
  349. protected:
  350. struct Trie_node{
  351. char* data; //若不为空,表示从root到此结点构成一个单词
  352. Trie_node* branch[num_chars]; //分支
  353. Trie_node(); //构造函数
  354. };
  355. Trie_node* root; //根结点(指针)
  356. };
  357. // Test.cpp : Defines the entry point for the console application.
  358. //
  359. #include "stdafx.h"
  360. Trie::Trie_node::Trie_node() {
  361. data = NULL;
  362. for (int i=0; i<num_chars; ++i)
  363. branch[i] = NULL;
  364. }
  365. Trie::Trie():root(NULL) {}
  366. Trie::~Trie(){}
  367. int Trie::trie_search(const char* word, char* entry ) const {
  368. int position = 0; //层数
  369. char char_code;
  370. Trie_node *location = root; //从根结点开始
  371. while( location!=NULL && *word!=0 ) {
  372. if (*word >= 'A' && *word <= 'Z')
  373. char_code = *word-'A';
  374. else if (*word>='a' && *word<='z')
  375. char_code = *word-'a';
  376. else return 0;// 不合法的单词
  377. //转入相应分支指针
  378. location = location->branch[char_code];
  379. position++;
  380. word++;
  381. }
  382. //找到,获取数据,成功返回
  383. if ( location != NULL && location->data != NULL ) {
  384. strcpy(entry,location->data);
  385. return 1;
  386. }
  387. else return 0;// 不合法的单词
  388. }
  389. int Trie::insert(const char* word, const char* entry) {
  390. int result = 1, position = 0;
  391. if ( root == NULL ) root = new Trie_node; //初始插入,根结点为空
  392. char char_code;
  393. Trie_node *location = root; //从根结点开始
  394. while( location!=NULL && *word!=0 ) {
  395. if (*word>='A' && *word<='Z') char_code = *word-'A';
  396. else if (*word>='a' && *word<='z') char_code = *word-'a';
  397. else return 0;// 不合法的单词
  398. //不存在此分支
  399. if( location->branch[char_code] == NULL )
  400. location->branch[char_code] = new Trie_node; //创建空分支
  401. //转入分支
  402. location = location->branch[char_code];
  403. position++;word++; }
  404. if (location->data != NULL) result = 0;//欲插入的单词已经存在
  405. else { //插入数据
  406. location->data = new char[strlen(entry)+1]; //分配内存
  407. strcpy(location->data, entry); //给data赋值表明单词存在
  408. }
  409. return result;
  410. }
  411. int main(){
  412. Trie t;
  413. char entry[100];
  414. t.insert("a", "DET");
  415. t.insert("abacus","NOUN");
  416. t.insert("abalone","NOUN");
  417. t.insert("abandon","VERB");
  418. t.insert("abandoned","ADJ");
  419. t.insert("abashed","ADJ");
  420. t.insert("abate","VERB");
  421. t.insert("this", "PRON");
  422. if (t.trie_search("this", entry))
  423. cout<<"'this' was found. pos: "<<entry<<endl;
  424. if (t.trie_search("abate", entry))
  425. cout<<"'abate' is found. pos: "<<entry<<endl;
  426. if (t.trie_search("baby", entry))
  427. cout<<"'baby' is found. pos: "<<entry<<endl;
  428. else
  429. cout<<"'baby' does not exist at all!"<<endl;
  430. }
  431. 3. 查找分析
  432. trie树中查找一个关键字的时间和树中包含的结点数无关,而取决于组成关键字的字符数。而二叉查找树的查找时间和树中的结点数有关O(log2n)。
  433. 如果要查找的关键字可以分解成字符序列且不是很长,利用trie树查找速度优于二叉查找树。如:
  434. 若关键字长度最大是5,则利用trie树,利用5次比较可以从26^511881376个可能的关键字中检索出指定的关键字。而利用二叉查找树至少要进行次比较。
  435. 3. trie树的应用:
  436. 1. 字符串检索,词频统计,搜索引擎的热门查询
  437. 事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。
  438. 举例:
  439. 1)有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
  440. 2)给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
  441. 3)给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。
  442. 41000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串
  443. 5)寻找热门查询:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G
  444. 2. 字符串最长公共前缀
  445. Trie树利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。举例:
  446. 1) 给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少. 解决方案:
  447. 首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线 Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。
  448. 而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:
  449. 1. 利用并查集(Disjoint Set),可以采用采用经典的Tarjan 算法;
  450. 2. 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;
  451. 3. 排序
  452. Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。
  453. 举例: 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。
  454. 4 作为其他数据结构和算法的辅助结构
  455. 如后缀树,AC自动机等。

发表评论

表情:
评论列表 (有 0 条评论,380人围观)

还没有评论,来说两句吧...

相关阅读

    相关 trie字典实现

    实现了一个简单的字典树. 假设所有的字符只有26个小写字母,并且除了节点出现的次数之外还增加了类似map功能的索引。 假如不只有26个字母,需要相应的做一些修改。

    相关 PHP实现Trie字典

    Trie树的概念(百度的解释):字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被

    相关 Trie字典

    1. Trie树 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索

    相关 字典trie

    字典树   又称单词查找树,[Trie树][Trie],是一种[树形结构][Link 1],是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符