实验4 表达树
目标
l 利用链式树结构,创建表达树的具体实现
l 开发一个逻辑表达树的实现,用它实现模拟一个简单的逻辑电路
l 创建一个表达树的复制
l 分析在表达树的实现中如何进行前序、中序、后序遮历
概述
通常按线性格式书写一个算术表达式,但在计算的时候,需要把它们作为一个分层实体。例如,在计算算术表达式: (1+3)*(6-4),首先计算1加3,然后是6减去4 。最后将这两个中间结果相乘,得到表达式的值。在执行这些计算时,我们已经潜意识地将这些运算分层,乘法运算建立在加法和减法运算的基础上。
可以利用下面这个二叉树来清楚地表示这个层次。这样的树就叫做表达树,如下图所示。
表达树
n 数据项: 在表达树中,每一个节点不是一个算术运算符就是一个数值。
n 结构: 节点构成树,树中每一个包含算术运算符的节点都有一对子节点,每个子节点都是一棵描述运算符操作数的子树的根。包含数值的节点没有子节点。
n 运算
InitExprTree()
要求:无
结果:创建一棵空表达树。
DeExprTree()
要求:无
结果:释放(free)用来存储表达树的空间。
voidBuild( )
要求:无
结果:从键盘以前缀的形式读人一个算术表达式,并且建立相应的表达树。
voidExpression( )
要求:无
结果:以带完全括弧的中缀形式输出相应的算术表达式。
floatEvaluate( )
要求:表达树非空。
结果:返回相应的算术表达式的值。
voidClear( )
要求:无
结果:删除表达树中的所有数据项。
voidShowStructure( )
要求:无
结果:从左(根)向右(叶)输出带有枝干的表达树,也就是说,将树从常规的方向逆时针旋转90度输出。如果树是空的,则输出“ Empty tree ”。这个运算只是为了测的调试目的。 假设算术表达式只仅含一位的非负整数以及算术运算符加、减、乘、除。
通常,我们以中缀形式书写算术表达式,即将每个运算符放在它的两个操作数之间: ( 1 + 3 )* ( 6 - 4 )。
在这个实验中,我们根据一个前缀算术表达式来创建一棵表达树,在前缀形式中,每个运算符紧挨它的操作数放在前边。上面的表达式用前缀的形式表示为
* + 1 3 -6 4
当我们从左向右处理前缀表达式时,按照定义,我们会将每个运算符与紧跟着它的操作数进行匹配。如果我们预先知道每个运算符拥有的操作数个数,就可以用下面的递归过程来建立相应的表达树。
读取下一个算术运算符或者数值。
创建一个包含运算符或者数值的节点。
if 节点包含运算符
then递归建立与运算符的操作数对应的子树。
else节点是叶子节点。
如果利用这个过程处理算术表达式: * + 1 3- 6 4,则创建出如下图所示的表达树结构。
在处理这个算术表达式时,我们假设所有的数是一位的、非负整数,这样,所有的数值都用一个字符表示。如果我们想让这个过程一般化,能处理多位数,就必须在表达式中使用分界符来分隔数字。
实验4 作业单
姓名小组_____________________ 日期_____________________
请在教师布置的练习时应的已布置列上打一个钩(√)。在提交这个实验的一组材料前面附上这个作业单。
练习 | 已布置:打钩或 列出练习编号 | 已完成 |
实验前练习 |
|
|
过渡练习 |
|
|
实验中练习1 |
|
|
实验中练习2 |
|
|
实验中练习3 |
|
|
实验后练习1 |
|
|
实验后练习2 |
|
|
总计 |
|
|
实验4 实验前练习
姓名小组_____________________ 日期_____________________
在这个练习中,我们将用递归函数实现表达树中的运算。
第一步:利用链接树结构实现表达树中的运算。假设算术表达式只包含一位的非负整数(‘0 ’.. ‘9’)以及四个基本的算术运算符(‘+’、‘-’、‘*’和‘/’)。并且假设每个算术表达式都是以前缀的形式从键盘输人, 所有的字符都在一行上。利用在前面的实验中开发的线性结构, 链接树结构实现需要使用两个类:一个是树的节点结构(ExprTreeNode),另一个是总体树结构(ExptrTree)。树的每个节点都应包含一个字符(dataItem)和一对指向两个子节点的指针(left和right)。还应该包括一个指向根节点(root)的指针。在文件exptree.h中定义表达树的类型。 在文件testexprtree.cpp中实现ShowStructured运算。
structExprTreeNode {
char dataItem;
ExprTreeNode *left, *right;
};
voidInitExprTreeNode ( ExprTreeNode *root, char elem,
structExprTreeNode *leftPtr,
structExprTreeNode *rightPtr );
InitExprTree(ExprTreeNode *root );
DeExprTree(ExprTreeNode *root );
voidbuild( ExprTreeNode *root );
voidexpression( ExprTreeNode *root );
floatevaluate( ExprTreeNode *root );
voidclear( ExprTreeNode *root );
voidshowStructure( ExprTreeNode *root )
voidshowSub( ExprTreeNode *root, ExprTreeNode *p, int level );
第二步:在exprtree.cpp中实现表达树各函数。
实验4 过渡练习
姓名小组_____________________ 日期_____________________
检查在实验之前或者实验期间你是否完成了这个练习。测试我们创建的表达树的实现testexprtree.cpp。
Ø第一步:编译在文件exprtree.cpp中创建的表达树的实现。
Ø第二步:编译文件testexprtree.cpp中的测试程序。
Ø第三步:运行第一步和第二步中产生的目标文件。
Ø第四步:通过为每一个算术表达式填写预期结果,完成测试计划。可能希望在测试计划中添加算术表达式。
Ø第五步:执行这个测试计划。如果在执行过程中,发现表达树的实现有错,纠正它们,并且重新执行测试计划。
表达树运算测试计划
测试项目 | 算术表达式 | 预期结果 | 检查 |
一个运算符 嵌套运算符 所有运算符在前 不均衡嵌套 整除 一位数 | +34 *+34/52 -/*9321 *4+6-75 /02 7 | 7 17.5 12.5 32 0 7 | 7 17.5 12.5 32 0 7 |
实验4 实验中练习1
姓名小组_____________________ 日期_____________________
计算机是由逻辑电路构成的,逻辑电路又是由一系列布尔输入值产生布尔输出结果获得的。可以用布尔逻辑运算符组成的表达式来表示从输入到输出的这种映射,布尔逻辑运算符包括AND、OR和NOT,并且布尔值为True(1)和False(0),如下表所示。
| (NOT) |
|
| (AND) | (OR) |
A | -A | A | B | A*B | A+B |
0 1 | 1 0 | 0 0 1 1 | 0 1 0 1 | 0 0 0 1 | 0 1 1 1 |
就像根据算术表达式创建一个算术表达树一样,也可以根据一个逻辑表达式创建一个逻辑表达树。例如,下面这个逻辑表达式:( 1 * 0 )+ ( 1 * - 0 ),可用前缀表示为: + * 1 0* 1 - 0
对这个表达式可产生下图所示的这个逻辑表达树。
计算这棵树的布尔值为True (1)。
构造这棵树要求处理一元运算符,即布尔运算符NOT (‘- ’)。 当建立一个逻辑表达树时,必须为每一个包含NOT运算符的节点,设置指向其操作数的右子树,并且将其左子树设为空。请注意,当执行其余运算时,小心不要遍历这些空的左子树。
Ø第一步:在文件exprtree.h中修改evaluate()函数的原型,使这个函数处理一个整型值,而不是浮点数。将产生的类声明保存在文件logitree.h中。
Ø第二步:基于文件logitree.h声明中的各种实现,来创建支持逻辑表达式的表达树逻辑表达树的实现,保存在文件logitree.cpp中。逻辑表达式由布尔值真和假(‘1’和‘0')和布尔运算符AND(*)、OR(+)和NOT(-)组成。将逻辑表达树的实现保存在文件logitree.cpp中。
Ø第三步:在文件testlogitree.cpp中修改测试程序,即,用逻辑表达树的头文件(logitree.h)代替算术表达树的头文件。
Ø第四步:编译和链接我们的逻辑表达树实现,并且修改测试程序。
Ø第五步:通过填充每个逻辑表达式的预期结果,完成以下测试计划。我们可能希望在测试计划中包括其他的逻辑表达式。
Ø第六步:执行这个测试计划,如果在执行过程中发现了创建的逻辑表达树的实现有错,纠正它们,并且重新执行测试计划。
逻辑表达树运算测试计划
测试项目 | 逻辑表达式 | 预期结果 | 检查 |
一个运算符 嵌套运算符 NOT(布尔值) NOT(子表达式) NOT(嵌套表达式) 双重否定 布尔值 | +10 *+10+01 +*10*1-0 +-1-*11 -*+110 --1 1 |
| 1 1
|
已经产生了一个构造和计算逻辑表达树的工具,我们可以利用这个工具来研究逻辑线路的应用,执行二进制算术运算。假设有两个一位的二进制数(X和Y),可以将它们加在一起产生一个一位的和(S)以及一个一位的进位结果(C)。一位的所有X和Y的组合二进制加法结果列表如下表所示。
简单地分析这个表格,可以发现,利用下面两个前缀逻辑表达式,我们能够计算出输出值S和C的值。
C =*XY S= +*X-Y*-XY
Ø 第七步:利用我们创建的逻辑表达树和修改后的测试计划,通过完成下面这个表格确认这些表达式的正确性。
X | Y | C = *XY | S = +*X-Y*-XY |
0 0 1 1 | 0 1 0 1 | *00 = *01 = *10 = *11 = | +*0-0*-00 = +*0-1*-01 = +*1-0*-10 = +*1-1*-11 = |
实验4 实验中练习2
姓名小组_____________________ 日期_____________________
在这个练习中,我们将为表达树结构创建一个复制函数。
ExprTreeNode* ExprTreeCopy( ExprTreeNode *T )
要求:无
结果:创建一个T的副本。不论何时,利用值调用将一个表达树传递给一个函数,或者一个函数返回一个表达树,或者利用另一个表达树初始化一个表达树时。
Ø 第一步:完成这个运算,并且将它添加到文件exprtree.cpp中。在文件exprtree.h中声明中有这个运算的原型。
Ø 第二步:在文件testexprtree.cpp中为复制函数写测试程序。
Ø 第三步:为这个运算准备一个测试计划。它要包括各种类型的表达树,包括空树和单数据项的树。测试计划表格如下。
Ø 第四步:执行测试计划。如果我们在复制构造函数中发现错误,纠正它们,然后重新执行测试计划。
复制构造函数测试计划
测试项目 | 算术表达式 | 预期结果 | 检查 |
|
|
|
|
实验4 实验中练习3
姓名小组_____________________ 日期_____________________
u数学中的交换律。例如a+b=b+a。交换一个表达式的加号的操作数,所产生的表达式的值不变。但是,交换律不是对所有的运算符都成立。例如,-a/b一般情况下不等于b/a-的值。
u所有二进制运算符的操作数都是可以交换的。交换一个算术表达式的运算符,要求在对应的表达树中重新建立节点。例如在表达树中,交换每一个运算符
产生表达树
u交换表达树的运算描述
voidcommute( )
要求:无
结果:交换表达树中的每个运算符的两个操作数。
Ø第一步:实现这个运算,并且将它添加到文件exprtree.cpp中,将这个运算的原型包含在文件exprtree.h中。
Ø第二步:在文件testexprtree.cpp中为commute运算编写测试程序。
Ø第三步:为这个包含多个不同表达树的运算准备一个测试计划。测试计划表格如下。
Ø第四步:执行测试计划。如果我们在commute运算的实现中发现错误,纠正它们,然后重新执行测试计划
commute 运算测试计划
测试项目 | 算术表达式 | 预期结果 | 检查 |
|
|
|
|
实验4 实验后练习1
姓名小组_____________________ 日期_____________________
用什么类型的遍历(前序、中序、后序)作为下面每一个表达树的基本运算实现的基础?并且简要解释一下,为什么要用给出的遍历来实现特定的运算。
建立(build) 遍历: 理由:
|
表达式(expression) 遍历: 理由:
|
计算(evaluate) 遍历: 理由:
|
清除(clear) 遍历: 理由:
|
实验4 实验后练习2
姓名小组_____________________ 日期_____________________
考虑以下函数 writeSub1( )和writeSub2( )
void writeSub1( ExprTreeNode *p )
{
if (p != 0)
{
writeSub1(p->left);
cout<<p->dataItem;
writeSub1(p->right);
}
}
void writeSub2 (ExprTreeNode *p)
{
if(p->left != 0) writeSub2(p->left);
cout<<p->dataItem;
if(p->right != 0) writeSub2(p->right);
}
让root指向一个非空表达树的根节点。下面两个函数调用能产生相同的结果吗?
writeSub1( root ) ;
writeSub2( root ) ;
如果不能,为什么? 如果能,这两个函数的作用有何不同,并且说明这种差别为什么是重要的。