6天通吃树结构—— 第一天 二叉查找树

原文:6天通吃树结构—— 第一天 二叉查找树

一直很想写一个关于树结构的专题,再一个就是很多初级点的码农会认为树结构无用论,其实归根到底还是不清楚树的实际用途。

一:场景:

1:现状

前几天我的一个大学同学负责的网站出现了严重的性能瓶颈,由于业务是写入和读取都是密集型,如果做缓存,时间间隔也只能在30s左

右,否则就会引起客户纠纷,所以同学也就没有做缓存,通过测试发现慢就慢在数据读取上面,总共需要10s,天啊...原来首页的加载关联

到了4张表,而且表数据中最多的在10w条以上,可以想象4张巨大表的关联,然后就是排序+范围查找等等相关的条件,让同学抓狂。

2:我个人的提供解决方案

① 读取问题

既然不能做缓存,那没办法,我们需要自己维护一套”内存数据库“,数据如何组织就靠我们的算法功底了,比如哈希适合等于性的查找,

树结构适合”范围查找“,lucene适合字符串的查找,我们在添加和更新的时候同时维护自己的内存数据库,最终杜绝表关联,老同学,还

是先应急,把常用的表灌倒内存,如果真想项目好的话,改架构吧...

② 添加问题

或许你的Add操作还没有达到瓶颈这一步,如果真的达到了那就看情况来进行”表切分“,”数据库切分“吧,让用户的Add或者Update

操作分流,虽然做起来很复杂,但是没办法,总比用户纠纷强吧,可对...

二:二叉查找树

正式切入主题,从上面的说明我们知道了二叉树非常适合于范围查找,关于树的基本定义,这里我就默认大家都知道,我就直接从

查找树说起了。

1:定义

查找树的定义非常简单,一句话就是左孩子比父节点小,右孩子比父节点大,还有一个特性就是”中序遍历“可以让结点有序。

2:树节点

为了具有通用性,我们定义成泛型模板,在每个结点中增加一个”数据附加域”。

 1     /// <summary>
 2     /// 二叉树节点
 3     /// </summary>
 4     /// <typeparam name="K"></typeparam>
 5     /// <typeparam name="V"></typeparam>
 6     public class BinaryNode<K, V>
 7     {
 8         /// <summary>
 9         /// 节点元素
10         /// </summary>
11         public K key;
12
13         /// <summary>
14         /// 节点中的附加值
15         /// </summary>
16         public HashSet<V> attach = new HashSet<V>();
17
18         /// <summary>
19         /// 左节点
20         /// </summary>
21         public BinaryNode<K, V> left;
22
23         /// <summary>
24         /// 右节点
25         /// </summary>
26         public BinaryNode<K, V> right;
27
28         public BinaryNode() { }
29
30         public BinaryNode(K key, V value, BinaryNode<K, V> left, BinaryNode<K, V> right)
31         {
32             //KV键值对
33             this.key = key;
34             this.attach.Add(value);
35
36             this.left = left;
37             this.right = right;
38         }
39     }

3:添加

根据查找树的性质我们可以很简单的写出Add的代码,一个一个的比呗,最终形成的效果图如下

这里存在一个“重复节点”的问题,比如说我在最后的树中再插入一个元素为15的结点,那么此时该怎么办,一般情况下,我们最好

不要在树中再追加一个重复结点,而是在“重复节点"的附加域中进行”+1“操作。

 1        #region 添加操作
 2         /// <summary>
 3         /// 添加操作
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <param name="value"></param>
 7         public void Add(K key, V value)
 8         {
 9             node = Add(key, value, node);
10         }
11         #endregion
12
13         #region 添加操作
14         /// <summary>
15         /// 添加操作
16         /// </summary>
17         /// <param name="key"></param>
18         /// <param name="value"></param>
19         /// <param name="tree"></param>
20         /// <returns></returns>
21         public BinaryNode<K, V> Add(K key, V value, BinaryNode<K, V> tree)
22         {
23             if (tree == null)
24                 tree = new BinaryNode<K, V>(key, value, null, null);
25
26             //左子树
27             if (key.CompareTo(tree.key) < 0)
28                 tree.left = Add(key, value, tree.left);
29
30             //右子树
31             if (key.CompareTo(tree.key) > 0)
32                 tree.right = Add(key, value, tree.right);
33
34             //将value追加到附加值中(也可对应重复元素)
35             if (key.CompareTo(tree.key) == 0)
36                 tree.attach.Add(value);
37
38             return tree;
39         }
40         #endregion

4:范围查找

这个才是我们使用二叉树的最终目的,既然是范围查找,我们就知道了一个”min“和”max“,其实实现起来也很简单,

第一步:我们要在树中找到min元素,当然min元素可能不存在,但是我们可以找到min的上界,耗费时间为O(logn)。

第二步:从min开始我们中序遍历寻找max的下界。耗费时间为m。m也就是匹配到的个数。

最后时间复杂度为M+logN,要知道普通的查找需要O(N)的时间,比如在21亿的数据规模下,匹配的元素可能有30个,那么最后

的结果也就是秒杀和几个小时甚至几天的巨大差异,后面我会做实验说明。

 1         #region 树的指定范围查找
 2         /// <summary>
 3         /// 树的指定范围查找
 4         /// </summary>
 5         /// <param name="min"></param>
 6         /// <param name="max"></param>
 7         /// <returns></returns>
 8         public HashSet<V> SearchRange(K min, K max)
 9         {
10             HashSet<V> hashSet = new HashSet<V>();
11
12             hashSet = SearchRange(min, max, hashSet, node);
13
14             return hashSet;
15         }
16         #endregion
17
18         #region 树的指定范围查找
19         /// <summary>
20         /// 树的指定范围查找
21         /// </summary>
22         /// <param name="range1"></param>
23         /// <param name="range2"></param>
24         /// <param name="tree"></param>
25         /// <returns></returns>
26         public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, BinaryNode<K, V> tree)
27         {
28             if (tree == null)
29                 return hashSet;
30
31             //遍历左子树(寻找下界)
32             if (min.CompareTo(tree.key) < 0)
33                 SearchRange(min, max, hashSet, tree.left);
34
35             //当前节点是否在选定范围内
36             if (min.CompareTo(tree.key) <= 0 && max.CompareTo(tree.key) >= 0)
37             {
38                 //等于这种情况
39                 foreach (var item in tree.attach)
40                     hashSet.Add(item);
41             }
42
43             //遍历右子树(两种情况:①:找min的下限 ②:必须在Max范围之内)
44             if (min.CompareTo(tree.key) > 0 || max.CompareTo(tree.key) > 0)
45                 SearchRange(min, max, hashSet, tree.right);
46
47             return hashSet;
48         }
49         #endregion

5:删除

对于树来说,删除是最复杂的,主要考虑两种情况。

<1>单孩子的情况

这个比较简单,如果删除的节点有左孩子那就把左孩子顶上去,如果有右孩子就把右孩子顶上去,然后打完收工。

<2>左右都有孩子的情况。

首先可以这么想象,如果我们要删除一个数组的元素,那么我们在删除后会将其后面的一个元素顶到被删除的位置,如图

那么二叉树操作同样也是一样,我们根据”中序遍历“找到要删除结点的后一个结点,然后顶上去就行了,原理跟"数组”一样一样的。

同样这里也有一个注意的地方,在Add操作时,我们将重复元素的值追加到了“附加域”,那么在删除的时候,就可以先判断是

不是要“-1”操作而不是真正的删除节点,其实这里也就是“懒删除”,很有意思。

 1         #region 删除当前树中的节点
 2         /// <summary>
 3         /// 删除当前树中的节点
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <returns></returns>
 7         public void Remove(K key, V value)
 8         {
 9             node = Remove(key, value, node);
10         }
11         #endregion
12
13         #region 删除当前树中的节点
14         /// <summary>
15         /// 删除当前树中的节点
16         /// </summary>
17         /// <param name="key"></param>
18         /// <param name="tree"></param>
19         /// <returns></returns>
20         public BinaryNode<K, V> Remove(K key, V value, BinaryNode<K, V> tree)
21         {
22             if (tree == null)
23                 return null;
24
25             //左子树
26             if (key.CompareTo(tree.key) < 0)
27                 tree.left = Remove(key, value, tree.left);
28
29             //右子树
30             if (key.CompareTo(tree.key) > 0)
31                 tree.right = Remove(key, value, tree.right);
32
33             /*相等的情况*/
34             if (key.CompareTo(tree.key) == 0)
35             {
36                 //判断里面的HashSet是否有多值
37                 if (tree.attach.Count > 1)
38                 {
39                     //实现惰性删除
40                     tree.attach.Remove(value);
41                 }
42                 else
43                 {
44                     //有两个孩子的情况
45                     if (tree.left != null && tree.right != null)
46                     {
47                         //根据二叉树的中顺遍历,需要找到”有子树“的最小节点
48                         tree.key = FindMin(tree.right).key;
49
50                         //删除右子树的指定元素
51                         tree.right = Remove(key, value, tree.right);
52                     }
53                     else
54                     {
55                         //单个孩子的情况
56                         tree = tree.left == null ? tree.right : tree.left;
57                     }
58                 }
59             }
60
61             return tree;
62         }
63         #endregion

三:测试

假如现在我们有一张User表,我要查询"2012/7/30 4:30:00"到"2012/7/30 4:40:00"这个时间段登陆的用户,我在txt中生成一个

33w的userid和time的数据,看看在33w的情况下读取效率如何...

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading;
  6 using System.IO;
  7 using System.Diagnostics;
  8
  9 namespace DataStruct
 10 {
 11     class Program
 12     {
 13         static void Main(string[] args)
 14         {
 15             List<long> list = new List<long>();
 16
 17             Dictionary<DateTime, int> dic = new Dictionary<DateTime, int>();
 18
 19             BinaryTree<DateTime, int> tree = new BinaryTree<DateTime, int>();
 20
 21             using (StreamReader sr = new StreamReader(Environment.CurrentDirectory + "//1.txt"))
 22             {
 23                 var line = string.Empty;
 24
 25                 while (!string.IsNullOrEmpty(line = sr.ReadLine()))
 26                 {
 27                     var userid = Convert.ToInt32(line.Split(new char[] { ‘,‘ }, StringSplitOptions.RemoveEmptyEntries)[0]);
 28
 29                     var time = Convert.ToDateTime(line.Split(new char[] { ‘,‘ }, StringSplitOptions.RemoveEmptyEntries)[1]);
 30
 31                     //防止dic出错,为了进行去重处理
 32                     if (!dic.ContainsKey(time))
 33                     {
 34                         dic.Add(time, userid);
 35
 36                         tree.Add(time, userid);
 37                     }
 38                 }
 39             }
 40
 41             var min = Convert.ToDateTime("2012/7/30 4:30:00");
 42
 43             var max = Convert.ToDateTime("2012/7/30 4:40:00");
 44
 45             var watch = Stopwatch.StartNew();
 46
 47             var result1 = dic.Keys.Where(i => i >= min && i <= max).Select(i => dic[i]).ToList();
 48
 49             watch.Stop();
 50
 51             Console.WriteLine("字典查找耗费时间:{0}ms,获取总数:{1}", watch.ElapsedMilliseconds, result1.Count);
 52
 53             watch = Stopwatch.StartNew();
 54
 55             var result2 = tree.SearchRange(min, max);
 56
 57             watch.Stop();
 58
 59             Console.WriteLine("二叉树耗费时间:{0}ms,获取总数:{1}", watch.ElapsedMilliseconds, result2.Count);
 60         }
 61     }
 62
 63     #region 二叉树节点
 64     /// <summary>
 65     /// 二叉树节点
 66     /// </summary>
 67     /// <typeparam name="K"></typeparam>
 68     /// <typeparam name="V"></typeparam>
 69     public class BinaryNode<K, V>
 70     {
 71         /// <summary>
 72         /// 节点元素
 73         /// </summary>
 74         public K key;
 75
 76         /// <summary>
 77         /// 节点中的附加值
 78         /// </summary>
 79         public HashSet<V> attach = new HashSet<V>();
 80
 81         /// <summary>
 82         /// 左节点
 83         /// </summary>
 84         public BinaryNode<K, V> left;
 85
 86         /// <summary>
 87         /// 右节点
 88         /// </summary>
 89         public BinaryNode<K, V> right;
 90
 91         public BinaryNode() { }
 92
 93         public BinaryNode(K key, V value, BinaryNode<K, V> left, BinaryNode<K, V> right)
 94         {
 95             //KV键值对
 96             this.key = key;
 97             this.attach.Add(value);
 98
 99             this.left = left;
100             this.right = right;
101         }
102     }
103     #endregion
104
105     public class BinaryTree<K, V> where K : IComparable
106     {
107         public BinaryNode<K, V> node = null;
108
109         #region 添加操作
110         /// <summary>
111         /// 添加操作
112         /// </summary>
113         /// <param name="key"></param>
114         /// <param name="value"></param>
115         public void Add(K key, V value)
116         {
117             node = Add(key, value, node);
118         }
119         #endregion
120
121         #region 添加操作
122         /// <summary>
123         /// 添加操作
124         /// </summary>
125         /// <param name="key"></param>
126         /// <param name="value"></param>
127         /// <param name="tree"></param>
128         /// <returns></returns>
129         public BinaryNode<K, V> Add(K key, V value, BinaryNode<K, V> tree)
130         {
131             if (tree == null)
132                 tree = new BinaryNode<K, V>(key, value, null, null);
133
134             //左子树
135             if (key.CompareTo(tree.key) < 0)
136                 tree.left = Add(key, value, tree.left);
137
138             //右子树
139             if (key.CompareTo(tree.key) > 0)
140                 tree.right = Add(key, value, tree.right);
141
142             //将value追加到附加值中(也可对应重复元素)
143             if (key.CompareTo(tree.key) == 0)
144                 tree.attach.Add(value);
145
146             return tree;
147         }
148         #endregion
149
150         #region 是否包含指定元素
151         /// <summary>
152         /// 是否包含指定元素
153         /// </summary>
154         /// <param name="key"></param>
155         /// <returns></returns>
156         public bool Contain(K key)
157         {
158             return Contain(key, node);
159         }
160         #endregion
161
162         #region 是否包含指定元素
163         /// <summary>
164         /// 是否包含指定元素
165         /// </summary>
166         /// <param name="key"></param>
167         /// <param name="tree"></param>
168         /// <returns></returns>
169         public bool Contain(K key, BinaryNode<K, V> tree)
170         {
171             if (tree == null)
172                 return false;
173             //左子树
174             if (key.CompareTo(tree.key) < 0)
175                 return Contain(key, tree.left);
176
177             //右子树
178             if (key.CompareTo(tree.key) > 0)
179                 return Contain(key, tree.right);
180
181             return true;
182         }
183         #endregion
184
185         #region 树的指定范围查找
186         /// <summary>
187         /// 树的指定范围查找
188         /// </summary>
189         /// <param name="min"></param>
190         /// <param name="max"></param>
191         /// <returns></returns>
192         public HashSet<V> SearchRange(K min, K max)
193         {
194             HashSet<V> hashSet = new HashSet<V>();
195
196             hashSet = SearchRange(min, max, hashSet, node);
197
198             return hashSet;
199         }
200         #endregion
201
202         #region 树的指定范围查找
203         /// <summary>
204         /// 树的指定范围查找
205         /// </summary>
206         /// <param name="range1"></param>
207         /// <param name="range2"></param>
208         /// <param name="tree"></param>
209         /// <returns></returns>
210         public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, BinaryNode<K, V> tree)
211         {
212             if (tree == null)
213                 return hashSet;
214
215             //遍历左子树(寻找下界)
216             if (min.CompareTo(tree.key) < 0)
217                 SearchRange(min, max, hashSet, tree.left);
218
219             //当前节点是否在选定范围内
220             if (min.CompareTo(tree.key) <= 0 && max.CompareTo(tree.key) >= 0)
221             {
222                 //等于这种情况
223                 foreach (var item in tree.attach)
224                     hashSet.Add(item);
225             }
226
227             //遍历右子树(两种情况:①:找min的下限 ②:必须在Max范围之内)
228             if (min.CompareTo(tree.key) > 0 || max.CompareTo(tree.key) > 0)
229                 SearchRange(min, max, hashSet, tree.right);
230
231             return hashSet;
232         }
233         #endregion
234
235         #region 找到当前树的最小节点
236         /// <summary>
237         /// 找到当前树的最小节点
238         /// </summary>
239         /// <returns></returns>
240         public BinaryNode<K, V> FindMin()
241         {
242             return FindMin(node);
243         }
244         #endregion
245
246         #region 找到当前树的最小节点
247         /// <summary>
248         /// 找到当前树的最小节点
249         /// </summary>
250         /// <param name="tree"></param>
251         /// <returns></returns>
252         public BinaryNode<K, V> FindMin(BinaryNode<K, V> tree)
253         {
254             if (tree == null)
255                 return null;
256
257             if (tree.left == null)
258                 return tree;
259
260             return FindMin(tree.left);
261         }
262         #endregion
263
264         #region 找到当前树的最大节点
265         /// <summary>
266         /// 找到当前树的最大节点
267         /// </summary>
268         /// <returns></returns>
269         public BinaryNode<K, V> FindMax()
270         {
271             return FindMin(node);
272         }
273         #endregion
274
275         #region 找到当前树的最大节点
276         /// <summary>
277         /// 找到当前树的最大节点
278         /// </summary>
279         /// <param name="tree"></param>
280         /// <returns></returns>
281         public BinaryNode<K, V> FindMax(BinaryNode<K, V> tree)
282         {
283             if (tree == null)
284                 return null;
285
286             if (tree.right == null)
287                 return tree;
288
289             return FindMax(tree.right);
290         }
291         #endregion
292
293         #region 删除当前树中的节点
294         /// <summary>
295         /// 删除当前树中的节点
296         /// </summary>
297         /// <param name="key"></param>
298         /// <returns></returns>
299         public void Remove(K key, V value)
300         {
301             node = Remove(key, value, node);
302         }
303         #endregion
304
305         #region 删除当前树中的节点
306         /// <summary>
307         /// 删除当前树中的节点
308         /// </summary>
309         /// <param name="key"></param>
310         /// <param name="tree"></param>
311         /// <returns></returns>
312         public BinaryNode<K, V> Remove(K key, V value, BinaryNode<K, V> tree)
313         {
314             if (tree == null)
315                 return null;
316
317             //左子树
318             if (key.CompareTo(tree.key) < 0)
319                 tree.left = Remove(key, value, tree.left);
320
321             //右子树
322             if (key.CompareTo(tree.key) > 0)
323                 tree.right = Remove(key, value, tree.right);
324
325             /*相等的情况*/
326             if (key.CompareTo(tree.key) == 0)
327             {
328                 //判断里面的HashSet是否有多值
329                 if (tree.attach.Count > 1)
330                 {
331                     //实现惰性删除
332                     tree.attach.Remove(value);
333                 }
334                 else
335                 {
336                     //有两个孩子的情况
337                     if (tree.left != null && tree.right != null)
338                     {
339                         //根据二叉树的中顺遍历,需要找到”有子树“的最小节点
340                         tree.key = FindMin(tree.right).key;
341
342                         //删除右子树的指定元素
343                         tree.right = Remove(tree.key, value, tree.right);
344                     }
345                     else
346                     {
347                         //单个孩子的情况
348                         tree = tree.left == null ? tree.right : tree.left;
349                     }
350                 }
351             }
352
353             return tree;
354         }
355         #endregion
356     }
357 }

比普通的dictionary效率还仅仅是快11倍,从数量级来说还不是非常明显,为什么说不是非常明显,这是因为普通的查找树的时间复杂度

不是严格的log(N),在最坏的情况下会出现“链表”的形式,复杂度退化到O(N),比如下图。

不过总会有解决办法的,下一篇我们继续聊如何旋转,保持最坏复杂度在O(logN)。

时间: 2024-10-12 04:12:30

6天通吃树结构—— 第一天 二叉查找树的相关文章

6天通吃树结构—— 第三天 Treap树

原文:6天通吃树结构-- 第三天 Treap树 我们知道,二叉查找树相对来说比较容易形成最坏的链表情况,所以前辈们想尽了各种优化策略,包括AVL,红黑,以及今天 要讲的Treap树. Treap树算是一种简单的优化策略,这名字大家也能猜到,树和堆的合体,其实原理比较简单,在树中维护一个"优先级“,”优先级“ 采用随机数的方法,但是”优先级“必须满足根堆的性质,当然是“大根堆”或者“小根堆”都无所谓,比如下面的一棵树: 从树中我们可以看到: ①:节点中的key满足“二叉查找树”. ②:节点中的“优

6天通吃树结构—— 第二天 平衡二叉树

原文:6天通吃树结构-- 第二天 平衡二叉树 上一篇我们聊过,二叉查找树不是严格的O(logN),导致了在真实场景中没有用武之地,谁也不愿意有O(N)的情况发生, 作为一名码农,肯定会希望能把“范围查找”做到地球人都不能优化的地步. 当有很多数据灌到我的树中时,我肯定会希望最好是以“完全二叉树”的形式展现,这样我才能做到“查找”是严格的O(logN), 比如把这种”树“调正到如下结构. 这里就涉及到了“树节点”的旋转,也是我们今天要聊到的内容. 一:平衡二叉树(AVL) 1:定义 父节点的左子树

6天通吃树结构—— 第五天 Trie树

原文:6天通吃树结构-- 第五天 Trie树 很有段时间没写此系列了,今天我们来说Trie树,Trie树的名字有很多,比如字典树,前缀树等等. 一:概念 下面我们有and,as,at,cn,com这些关键词,那么如何构建trie树呢? 从上面的图中,我们或多或少的可以发现一些好玩的特性. 第一:根节点不包含字符,除根节点外的每一个子节点都包含一个字符. 第二:从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串. 第三:每个单词的公共前缀作为一个字符节点保存. 二:使用范围 既然

6天通吃树结构—— 第四天 伸展树

原文:6天通吃树结构-- 第四天 伸展树 我们知道AVL树为了保持严格的平衡,所以在数据插入上会呈现过多的旋转,影响了插入和删除的性能,此时AVL的一个变种 伸展树(Splay)就应运而生了,我们知道万事万物都遵循一个“八二原则“,也就是说80%的人只会用到20%的数据,比如说我们 的“QQ输入法”,平常打的字也就那么多,或许还没有20%呢. 一:伸展树 1:思想 伸展树的原理就是这样的一个”八二原则”,比如我要查询树中的“节点7”,如果我们是AVL的思路,每次都查询“节点7”,那么当这 棵树中

【查找结构 2】二叉查找树 [BST]

当所有的静态查找结构添加和删除一个数据的时候,整个结构都需要重建.这对于常常需要在查找过程中动态改变数据而言,是灾难性的.因此人们就必须去寻找高效的动态查找结构,我们在这讨论一个非常常用的动态查找树——二叉查找树 . 二叉查找树的特点 下面的图就是两棵二叉查找树,我们可以总结一下他的特点: (1) 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值 (2) 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值(3) 它的左.右子树也分别为二叉查找树 我们中序遍历这两棵树发现一个

Q114第一颗二叉查找树(链式)

输入n,然后n个树,建立二叉查找树.从小到大输出每个节点的左右子树,空输出# #include<cstdio> #include<iostream> using namespace std; typedef struct node{ int data; struct node *lchild,*rchild; }NODE; void input(NODE *root,int value){ if(value==root->data){ return; } else if(va

WEB元素的结构-----第一课

1. 文档类型声明:严格型(Strict Mode),过渡型(Transitional Mode),框架型(Frameset Mode) 严格型:<!DOCUTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml/DTD/xhtml11-strict.dtd"> 过渡型:<!DOCUTYPE html PUBLIC "-//W3C//D

【实习记】2014-08-28知值求范围问题

接到一个优化算法任务数据库储存着银行卡号用上下限表示的区间,互不交叉重叠,现有9万多记录.给一个卡号,如何找到该条记录.现有方法是使用前三位数做索引字段,起到一定效果,但是数据一大了还是效率低. 我推测了一下其应用情景是银行每个网点所具有的发卡权不一样,某个区间属于某个网点所发. 阶段一.IP反查城市 既然不是一下子能想到答案的,我首先想到的是,是否已有解决方案.我想到了一个类似但普遍的应用情景:ip反查城市 于是谷歌关键字搜索. 五种算法实现IP到地址的转换http://www.haogong

Tire树(字典树)

from:https://www.cnblogs.com/justinh/p/7716421.html Trie,又经常叫前缀树,字典树等等.它有很多变种,如后缀树,Radix Tree/Trie,PATRICIA tree,以及bitwise版本的crit-bit tree.当然很多名字的意义其实有交叉. 定义 在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串.与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定.一个节点的所有