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

上一篇文章中谈到的前缀树实现方式,时间复杂度从理论上来讲已经达到了最优,而空间复杂度理论 上也可以做到较优。但是理论和实际是有差别的,而对于上文前缀树的实现来说,这两方面并不是非常理 想:

时间:前缀树时间复杂度为O(m)的前提是每次哈希表查找操作的时间复杂度为O(1),不过这个O(1)与 一次数值比较相比,从性能上来说还是有比较明显的差距。

空间:前缀树空间复杂度较优的前提是“精细”地实现该数据结构,如果像上文般粗枝大叶,那么会 形成大量稀疏的哈希表,反而造成空间浪费。

因此,虽然事实上前缀树是老赵第一个真正实现的缓存方法,但是对此并不满意,也想着有什么办法 可以进行优化。

之前提到过,使用字符串进行完整编码的性能较低,其原因之一是因为只有完整编码才能获得最终结 果,继而与字典中其他元素进行比较。很明显的事实是,比较两棵表达式树是否相同并不需要对它们进行 完整编码,如果我们“手动”进行比较,往往只要一个节点一个节点进行对比,只要找到某个节点不同, 便可以得到结论。不过仅仅比较两棵表达式是否相同无法进行查询和排序,我们至少要得到两个表达式树 之间的大小关系,这样我们才能对它们进行排序,才能够进行查找,例如我们可以将它们放入线性表中并 时刻保持排序状态,这样便可以进行二分查找了。

要得到两颗表达式树的大小关系,与得到它们是否相等的时间复杂度是完全一样的,事实上它们的实 现方式也几乎完全相同,只需在得到结果时返回1、0或-1,而不是一个简单的布尔值便可。做一次这样的 比较时间复杂度是O(m),m为遍历序列较短的表达式树的长度。不过就之前的分析可以得知,对于两个“ 随机”的表达式树进行比较的性能是相当高的,因为我们只要发现两者有一些不同,便可以立即返回结果 。

可惜的是,我们要实现一个这样的比较功能并不简单,因为ExpressionVisitor只能用于遍历单个表达 式树,无法同时操作两个。不过我们只要模仿ExpressionVisitor的遍历方式,“同时”遍历两个表达式 树就可以了。因此我们现在就来实现算法的核心功能:ExpressionComparer。如下:

public class ExpressionComparer : IComparer<Expression>
{
   public virtual int Compare(Expression x, Expression y)
   {
     int result;
     if (this.CompareNull(x, y, out result)) return result;

     result = this.CompareType(x.GetType(), y.GetType());
     if (result != 0) return result;

     result = x.NodeType - y.NodeType;
     if (result != 0) return result;

     result = this.CompareType(x.Type, y.Type);
     if (result != 0) return result;

     switch (x.NodeType)
     {
       case ExpressionType.Negate:
       case ExpressionType.NegateChecked:
       ...
         return this.CompareUnary((UnaryExpression)x, (UnaryExpression)y);
       case ExpressionType.Add:
       case ExpressionType.AddChecked:
       ...
         return this.CompareBinary((BinaryExpression)x, (BinaryExpression) y);
       case ExpressionType.TypeIs:
         return this.CompareTypeIs((TypeBinaryExpression)x,  (TypeBinaryExpression)y);
       case ExpressionType.Conditional:
         return this.CompareConditional((ConditionalExpression)x,  (ConditionalExpression)y);
       case ExpressionType.Constant:
         return this.CompareConstant((ConstantExpression)x,  (ConstantExpression)y);
       ...
       default:
         throw new Exception(string.Format("Unhandled expression type:  '{0}'", x.NodeType));
     }
   }

   ...
}

时间: 2024-11-09 00:20:05

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

二叉搜索树与树的遍历非递归练习

复习了二叉搜索树的实现,包括插入.查找和删除操作,顺便做了下二叉树的三种遍历操作.全部操作采用非递归方式.   #include<iostream>   #include<stack>   using namespace std;     typedef int T;   // 值类型      // 节点定义    struct node {       T val;       node *left,*right;       node(const T& val):va

纸上谈兵: 树, 二叉树, 二叉搜索树[转]

树的特征和定义 树(Tree)是元素的集合.我们先以比较直观的方式介绍树.下面的数据结构是一个树: 树有多个节点(node),用以储存元素.某些节点之间存在一定的关系,用连线表示,连线称为边(edge).边的上端节点称为父节点,下端称为子节点.树像是一个不断分叉的树根. 每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent).比如说,3,5是6的子节点,6是3,5的父节点:1,8,7是3的子节点, 3是1,8,7的父节点.树有一个没有父节点的节点,称为根节点(

纸上谈兵: 树, 二叉树, 二叉搜索树

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   树的特征和定义 树(Tree)是元素的集合.我们先以比较直观的方式介绍树.下面的数据结构是一个树: 树有多个节点(node),用以储存元素.某些节点之间存在一定的关系,用连线表示,连线称为边(edge).边的上端节点称为父节点,下端称为子节点.树像是一个不断分叉的树根. 每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent).比如说,3,5

[华为机试练习题]33.二叉搜索树

题目 描述: 判断两序列是否为同一二叉搜索树序列 题目类别: 树 难度: 中级 运行时间限制: 10Sec 内存限制: 128MByte 阶段: 入职前练习 输入: 开始一个数n,(1<=n<=20) 表示有n个需要判断,n= 0 的时候输入结束. 接下去一行是一个序列,序列长度小于10,包含(0~9)的数字,没有重复数字,根据这个序列可以构造出一颗二叉搜索树. 接下去的n行有n个序列,每个序列格式跟第一个序列一样,请判断这两个序列是否能组成同一颗二叉搜索树. 输出: 如果序列相同则输出YES

九度题目1009:二叉搜索树

题目1009:二叉搜索树 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:4308 解决:1919 题目描述: 判断两序列是否为同一二叉搜索树序列 输入: 开始一个数n,(1<=n<=20) 表示有n个需要判断,n= 0 的时候输入结束. 接下去一行是一个序列,序列长度小于10,包含(0~9)的数字,没有重复数字,根据这个序列可以构造出一颗二叉搜索树. 接下去的n行有n个序列,每个序列格式跟第一个序列一样,请判断这两个序列是否能组成同一颗二叉搜索树. 输出: 如果序列相同则输出YES

二叉搜索树与双向链表的C++实现

题目:输入一颗二叉搜索树, 将该二叉搜索树转换成一个排序的双向链表. 要求不能创建任何新的结点, 只能调整数中结点的指针的指向. 方法: 使用中序遍历每一个结点, 并进行连接, 即左子树指前, 右子树指后, 并保存前一个节点. 本程序包含算法原理, 测试程序, 及 输出. 代码: /* * main.cpp * * Created on: 2014.6.12 * Author: Spike */ /*eclipse cdt, gcc 4.8.1*/ #include <iostream> #i

二叉搜索树(Binary Search Tree)--C语言描述(转)

图解二叉搜索树概念 二叉树呢,其实就是链表的一个二维形式,而二叉搜索树,就是一种特殊的二叉树,这种二叉树有个特点:对任意节点而言,左孩子(当然了,存在的话)的值总是小于本身,而右孩子(存在的话)的值总是大于本身. 下面来介绍在此种二叉树结构上的查找,插入,删除算法思路. 查找:因为这种结构就是为了来方便查找的,所以查找其中的某个值很容易,从根开始,小的往左找,大的往右找,不大不小的就是这个节点了: 代码很简单,这里就不写了. 插入:插入一样的道理,从根开始,小的往左,大的往右,直到叶子,就插入.

二叉搜索树的插入与删除(详细解析)_C 语言

题目:创建一个类,类中的数据成员时一棵二叉搜索树,对外提供的接口有添加结点和删除结点这两种方法.用户不关注二叉树的情况.要求我们给出这个类的结构以及实现类中的方法. 思路添加结点:添加结点其实很容易,我们只需要找到结点所行对应的位置就可以了,而且没有要求是平衡的二叉搜索树,因此每次添加结点都是在叶子结点上操作,不需要修改二叉搜索树整体的结构.要找出添加节点在二叉搜索树中的位置,可以用一个循环解决.判断插入结点与当前头结点的大小,如果大于头结点则继续搜索右子树,如果小于头结点则继续搜索左子树.直到

算法起步之二叉搜索树

原文:算法起步之二叉搜索树         学习二叉搜索树之前,我们先复习一下树的相关知识,数是几种经典的数据结构之一,树其实就是维护了一对多的关系,一个节点可以有多个孩子,但是每个节点至多只有一个双亲.如何去描述存储一棵树呢,这里方法有很多,数组.链表.十字链表等等.这里我们主要研究二叉树,二叉树是树的一种特殊形式,节点至多只有两棵子树,有左右之分次序不能任意颠倒.二叉树还有一个性质就是叶子节点的个数=度为2的节点+1 .二叉搜索树又叫二叉排序树或二叉查找树.他比二叉树又加个1个性质,就是左子

二叉搜索树源码分享_C 语言

复制代码 代码如下: #include <iostream>using namespace std; //枚举类,前中后三种遍历方式enum ORDER_MODE{ ORDER_MODE_PREV = 0, ORDER_MODE_MID, ORDER_MODE_POST}; //树节点的结构体template <class T>struct BinaryNode{ T    element; BinaryNode  *left; BinaryNode  *right;  Binar