谈表达式树的缓存(3):使用前缀树

在上一篇文章里我们设法将前缀树构造为一个唯一的字符串,然后使用字符串作为key缓存在字典中。 这个想法非常直接,做法也不困难(在遍历时记录详细信息便可)。不过事实上,老赵在思考表达式树的 缓存问题时,这种字符串拼接的方式只存在于脑海当中,而上文的实现是为了这一系列文章的完整性而特 地编写的。这是因为它的缺点较为明显,正如上文所述,字符串拼接操作较为耗时耗资源,且很容易生成 一个长度可观的字符串(并非不能优化,不过实现就复杂了)。于是我们现在设法选择另一个解决方案来 处理这个问题。

不过现在,我们先来考虑另一个问题:比较两个字符串是否相同(例如实现一个String.Equals方法) 。此时,我们往往会分几步进行:

判断字符串长度是否相等,如果不等,则两个字符串肯定不同。

从头遍历两个字符串,如果找到某一位不相同,则两个字符串相同。

如果完整遍历完整个字符串,则两者相同。

以上算法基本上是我们可以写出的最高效的比较算法。为什么这么说呢?原因主要有以下两点:

我们在比较时,首先使用最容易获得的信息(长度)进行判断,并且在合适的时候(发现某一位不同 )及时返回,节省了时间。

比较时不需要保存完整信息(比较字符串的第n位时,从0到n-1位字符无需保留),这样在空间上也节 省了下来。

那么我们来思考一下,为什么前一篇文章中谈到的字符串会性能差呢?其实他正是由于违反了上面的 “特点”:

需要对整个树进行完整编码,这样无法通过部分编码上排除各种状况,时间上耗费较大。

必须保留完整路径,造成字符串很长,导致空间上耗费也大。

与字符串比较略有不同,既然我们最终是要匹配完全相同的表达式树,那么一次完整的树遍历是不可 避免的,因此第一点可能很难有所改变。那么第二点呢?我们是否可以在进行匹配时,不用保存之前已经 遍历的节点,遍历到某个“冲突”则表明匹配失败,而经过一个完整遍历,则表明匹配成功。为了进一步 看清需求,我们再说的清楚一点:

对一颗表达式树进行完整遍历,可以看作是构造一个由各节点(或节点的“特性”)构成的序列。

在遍历序列时,无需保存之前遍历的结果。遍历结束时,则表示匹配成功。

“序列”、“遍历”、“匹配”……在心中反复默念几遍,您是否想到了些什么?没错,上述需求似 乎符合我们常用的数据结构:前缀树。

时间: 2024-08-04 11:07:04

谈表达式树的缓存(3):使用前缀树的相关文章

谈表达式树的缓存(5):引入散列值

到目前为止,我们已经实现了三种缓存方式:首先我们设法构建唯一字符串,但是由于它的代价较高 ,于是我们使用了前缀树进行存储:又由于前缀树在实际操作中所花的时间和空间都有不令人满意之处, 我们又引入了二叉搜索树.那么二叉搜索树又有什么缺点呢?其实前文已经谈到过了,那就是从理论上来 说,它的时间复杂度相对前两个要高,在最坏情况下将会出现O(m * log(n))的时间复杂度--每次比较 两个前缀树需要耗费O(m),共比较O(log(n))次. 很显然,与最理想的时间复杂度O(m)相比,其差距就在于n,

谈表达式树的缓存(4):使用二叉搜索树(AVL树)

上一篇文章中谈到的前缀树实现方式,时间复杂度从理论上来讲已经达到了最优,而空间复杂度理论 上也可以做到较优.但是理论和实际是有差别的,而对于上文前缀树的实现来说,这两方面并不是非常理 想: 时间:前缀树时间复杂度为O(m)的前提是每次哈希表查找操作的时间复杂度为O(1),不过这个O(1)与 一次数值比较相比,从性能上来说还是有比较明显的差距. 空间:前缀树空间复杂度较优的前提是"精细"地实现该数据结构,如果像上文般粗枝大叶,那么会 形成大量稀疏的哈希表,反而造成空间浪费. 因此,虽然事

谈表达式树的缓存(6):五种缓存方式的性能比较

开始还债,因为还有至少两个可写的重要话题依赖在这个系列上,不解决就难以前进. 目前我们已经涉及了五种不同的缓存实现,它们分别是: SimpleKeyCache:构造字符串作为Key,使用字典作为存储. PrefixTreeCache:使用前缀树进行存储. SortedListCache:使用排序列表或二叉搜索树进行存储. HashedListCache:先对表达式树取散列值,再从字典中取出二叉搜索树. DictionaryCache:实现了散列值和表达式树的比较方法,直接使用字典进行存储. 如果

谈表达式树的缓存(1):引言

表达式树(Expression Tree)是.NET 3.5中引入的一种表达方式.表达式树的运用十分广泛,可以直 观地表现出各种"数据",甚至"逻辑"和"行为".再者,表达式树是强类型的,因此合理地使用这个 新特性可以让代码编写变得优雅,方便.一个最简单而常见的例子便是,某些朋友目前就已经喜欢使用表 达式树来代替传统的ByXxx方法,尤其是在访问一些直接支持表达式树的数据源时(例如IEnumerable或 LINQ to SQL).如下: pub

迅雷刷树助手失效后临时刷树方法

这几天迅雷幸福树回档了大约11天的数据,迅雷刷树助手V1.4也随之失效.常规BT刷树或本地FTP等方法过于伤害硬盘,测试CE刷树依然可用,配合变速齿轮也可秒刷(数十秒显示新数据).迅雷刷树助手其实依然有效,只是操作方法略有不同--需要取消迅雷自动登录. 迅雷刷树助手新的刷树方法 以前的方法一般是: 自动登录迅雷--刷树--等待10分钟 新操作方法: 取消自动登录 打开迅雷--等待幸福树出现--点击登录--刷树--等待30分钟 原理不明,但这几天都有效. 根据迅雷的作风,幸福树阳光值的抽奖系统应该

表格树 收缩-ligerui中关于treegrid(表格树)初始化时如何让树是收缩的

问题描述 ligerui中关于treegrid(表格树)初始化时如何让树是收缩的 ligerui中关于treegrid(表格树)中在第一次初始化的时候如何让树是收缩的而不是展开的.我看了一下关于他的API,没有介绍是如何关闭的,请问有没有遇到的,如何处理的! 解决方案 采用延迟加载,先初始化最外层的节点,然后一层一层的加载.

谈表达式树的缓存(2):由表达式树生成字符串

谈到使用表达式树作为key进行缓存,您脑海中最早浮现出来的解决方案是什么?老赵看来,大部分朋 友的第一反应自然就是将作为key的表达式树,使用一定规则生成一个字符串.简而言之,这个生成字符 串的规则F需要能够保证: 在同一个缓存空间内,同样的表达式树能够生成相同的字符串. 在同一个缓存空间内,不同的表达式树生成不同的字符串. 似乎有些罗嗦,朋友们明白便是.其中"在同一个缓存空间"的前提,其实只是放宽了后续要求的条 件.因为在不同的缓存空间内,即使不同的表达式树生成了同样的字符串,它们也

谈表达式树的缓存(7):五种缓存方式的总体分析及改进方案

终于到了这个系列的最后一篇文章了,这个系列的文章本是许多话题的基础,却拖了那么长时间还没 有完结.这篇文章主要讨论五种缓存方式各自的优劣,以及他们的性能关键在什么地方,如果要进行改进 又有什么可选方案.在这个问题上,老赵的思考可能会有遗漏,如果您有任何补充,也请不吝指出. SimpleKeyCache SimpleKeyCache是最直接的缓存方案:将表达式树进行完整编码,将其转化为一个独一无二的字符串 ,然后作为Key存放到字典中. 从上一篇文章的实验来看,无论从耗时还是对GC造成的压力上来说

Hash树(散列树)和Trie树(字典树、前缀树)

1.Hash树 理想的情况是希望不经过任何比较,一次存取便能得到所查的记录, 那就必须在记的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和一个唯一的存储位置相对应.因而在查找时,只要根据这个对应关系f找到 给定值K的像f(K).由此,不需要进行比较便可直接取得所查记录.在此,我们称这个对应关系为哈希(Hash)函数,按这个思想建立的表为哈希表. 在哈希表中对于不同的关键字可能得到同一哈希地址,这种现象称做冲突.在一般情况下,冲突只能尽可能地减少,而不能完全避免.因为哈希函数是从