1、表达式(1+34)*5-56/7 的二叉树画法为:将运算符作为根结点,第一操作数作为左子树,第二个操作数作为右子树。结果为:
...........-
.......*........./
.....+...5...56..7
...1..34
2、表达式(1+34)*5-56/7 的后缀表达式应该为此二叉树的后序遍历序列。结果为:1 34 + 5 * 56 7 / -
----------------------
表达式求值程序实现要点:
1 表达式求值可分为二个步骤进行,第一步先将初始表达式转换成逆波兰表示(后序序列),第二步再对逆波兰表示求表达式的值。第一阶段中要使用一个栈暂存运算符,第二阶段中要使用一个栈存放运算量,表达式的后序序列作为中间结果存放在一个队列中。主要对象的定义如下:
char *str=new char[100];
SqStack<char> s1;
SqQueue<char> q;
SqStack<int> s2;
2 顺序扫描初始表达式,当遇到运算量时,可直接将其送入输出队列中;当遇到运算符时,要与栈顶的运算符进行优先级的比较,如果前者高则进栈,如果栈顶的运算 符优先级高(或等于)可将其从栈中弹出并送入输出队列中,然后再将当前运算符入栈;当遇到左括号时即入栈,当遇到右括号时将与其配对的左括号上面的运算符 全部依次弹出送入输出队列中,再删除该左括号;当遇到‘#’时将栈中运算符全部依次弹出送入输出队列中。经过第一阶段的处理后初始表达式“9-(2+ 4*7)/5+3#”将转换成后缀表达式:“9247*+5/-3+”。参考代码如下:
cin>>str;
s1.clear(); s1.push('#'); i=0;
do{ c=str[i++];
switch(c)
{case ' ':break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': q.enque(c);break;
case '(': s1.push(c);break;
case ')':
case '#': do{t=s1.pop();
if (t!='('&&t!='#')q.enque(t);
}while (t!='('&&t!='#');
break;
case '+':
case '-':
case '*':
case '/':
while(pri(c)<=pri(s1.gettop()))
{t=s1.pop(); q.enque(t);}
s1.push(c); break;
}
}while(c!='#');
3 对逆波兰表示求表达式值的算法较为简单:从队列中取出元素,若为运算量则进栈;否则为运算符,从运算量栈中依次弹出二个运算量,运算后将结构推入栈中,重复进行直至队列为空,计算结果在栈中。参考代码如下:
s2.clear();
while(!q.empt())
{c=q.dlque();
if(c>='0'&&c<='9')s2.push(c-'0');
else{y = s2.pop();
x = s2.pop();
switch(c)
{case '+': s2.push(x+y); break;
case '-': s2.push(x-y); break;
case '*': s2.push(x*y); break;
case '/': s2.push(x/y); break;
}
}
}
cout<<s2.gettop()<<endl;
4 函数pri返回优先数参考代码如下:
int pri(char op)
{ switch(op)
{case '(':
case '#': return 0;
case '-':
case '+': return 1;
case '*':
case '/': return 2;
}
};-
--------------------------------
其余转换
所有剩余的转换都可以轻易地用二分表达式树来完成。事实上,上面的两种转换,也即中缀->前缀和中缀>后缀,也能用二分树来做,但是技巧性太强,而用栈来完成就容易得多。现在我们继续讲解,首先对二分表达式树下一定义。
二分表达式树
表 达式树是严格的二分树,叶节点存放操作数,非叶节点存放运算符,根节点存放用于计算左子树和右子树计算结果的运算符。一旦我们得到了某一特定表达式的树, 将它转换成各种不同的表示形式(中缀,前缀和后缀)以及计算其值都只需要遍历这棵树就可以了。下图展示了前面的表达式2*3/(2-1)+5*(4-1) 对应的表达式树。
注意:二分表达式树不包含括号,原因在于用树结构计算表达式值的时候,表达式树本身的结构就已经决定了计算的顺序。
当 我们先序遍历(先访问根节点,再依次访问左儿子和右儿子)表达式树时,得到的是此表达式的前缀表示,类似地,后序遍历(先依次访问左儿子,右儿子,再访问 根节点)将得到后缀表示。如果中序遍历(依次访问左儿子,右儿子,根节点)表达式树我们会得到什么结果呢?对于不包含括号的表达式,中序遍历将得到它的确 定的中缀形式,但对那些用括号来改变运算符间优先次序的表达式,简单的中序遍历就不能将其还原成原来的中缀形式。
用表达式树完成各种表示形式间的转换
前缀->中缀
后面的算法适用于其中缀形式中没有用括号来改变运算符间原有优先次序的表达式。
1)根据前缀表达式创建一棵表达式树
2)中序遍历这棵树
前缀 ->后缀
1)根据前缀表达式创建一棵表达式树
2)后序遍历这棵树
你看这是多么容易啊!关键之处在于根据前缀表达式创建表达式树,下面的算法完成了这项工作:
根据前缀表达式创建表达式树的算法
1)求前缀表达式的逆序
2)检查输入的下一元素
3)假如是操作数,则
i)创建一个叶节点,也就是没有儿子的节点(node- >left_child=node->right_child=NULL)
ii)将操作数赋给叶节点的data分量
iii)叶节点地址入栈
4)假如是运算符,则
i)创建一个节点
ii)运算符赋给该节点的data分量
iii)栈顶节点地址出栈,并将其赋值给新节点的left分量,即node->left_child
iv)栈顶节点地址出栈,并将其赋值给新节点的right分量,即node->right_child
v)新节点地址入栈
5)假如输入还未结束,跳转到步骤2
6)假如输入结束,栈中节点地址出栈,此地址即为整个树的根节点地址
前缀表示转换成中缀和后缀表示的算法参考程序#2。
后缀->中缀
同前,这里的算法也是只适用于其中缀形式中没有用括号来改变运算符间原有优先次序的表达式。
1)由后缀表达式创建表达式树
2)中序遍历该树
后缀->前缀
1)由后缀表达式创建表达式树
2)先序遍历该树
根据后缀表达式创建表达式树的算法
1) 检查输入的下一元素
2)假如是操作数,则
i)创建一个叶节点,也就是没有儿子的节点(node- >left_child=node->right_child=NULL)
ii)将操作数赋给叶节点的data分量
iii)叶节点地址入栈
3) 假如是运算符,则
i)创建一个节点
ii)运算符赋给该节点的data分量
iii)栈顶节点地址出栈,并将其赋给新节点的right分量,即node->right_child
iv)栈顶节点地址出栈,并将其赋给新节点的left分量,即node->left_child
v)新节点地址入栈
4) 假如输入还未结束,跳转到步骤2
5) 假如输入结束,栈中节点地址出栈,此地址即为整个树的根节点地址
上述算法参考程序#2。
好了,最终我们可以完称表达式的任意两表示形式的转换了。这里我们对前面的内容作一小节:
1)中缀->前缀,用栈
2)中缀->后缀,用栈
3) 前缀->中缀,用表达式树
4) 前缀->后缀,用表达式树
5) 后缀->中缀,用表达式树
6) 后缀->前缀,用表达式树
现在我们所剩下的问题就是如何计算表达式的值了。计算一个表达式的包含两个步骤:
1)创建给定表达式所对应的二分树
2)递归地计算这棵树的值
(按照文中方法,对一个普通的中缀表达式我们要经过中缀->前缀或后缀->表达式树->递归算值的过程。其实也可以用栈直接由中缀形式计算表达式的值――译者注)
下面的函数将用递归的方法计算一个表达式树的值:
函数名:EvaluateTree,参数:根节点地址
int EvaluateTree (struct node* root)
IF root != NULL
IF current node contains an operator
x = EvaluateTree(root -> left_child)
y = EvaluateTree(root -> right_child)
Perform operation on x and y, specified by the
operator and store result in z
Return z
else Return root->data
Refer program #2 for evaluation of an Expression Tree.
参考程序#2计算表达式树的值的算法。
文中源代码: http://www.youkuaiyun.com/Develop/read_article.asp?id=27543
未完待续。
作者:Pradeep P. Chandiramani.
联系方式:pradeepchandiramani@yahoo.co.in
译者:liudows
联系方式:mysea000@163.com
------------
中序表达式的表示法与计算方法
这些东西在算法的书中都有讲到,以下是根据回忆做出的记录。
通常编程中的表达式计算均是从左往右读取,考虑运算符优先级,这些都是中序表达式。
(相比较于前序和后序主要是指操作符放在前面或后面,所以中序指操作符放在中间)
处理这种表达式时一般需要 2个栈来存放运算符和操作数,如果运算符的优先权高于下
一个操作符时,则从堆栈中取出操作数与该运算符进行计算成本。
下面请看一个简单例子:(呆会针对常见迷惑的PHP表达式计算作出理解)
$i + $x / $y - $j
0) 建立2个栈,操作数栈和运算符的栈,都是空的。
从左往右读取表达式,如果读到操作数就放入操作数栈中,运算符则放入运算符的栈中
1) 读到 $i
+-------+
| $i |
--+-------+-- --+-------+--
操作数栈 操作符栈
2) 读到 +
+-------+ +-------+
| $i | | + |
--+-------+-- --+-------+--
操作数栈 操作符栈
3) 读到 $x
+-------+
| $x |
+-------+ +-------+
| $i | | + |
--+-------+-- --+-------+--
操作数栈 操作符栈
3) 读到 / 由于 / 的优先级比栈中的 + 高, 则将 / 直接存入栈中
+-------+ +-------+
| $x | | / |
+-------+ +-------+
| $i | | + |
--+-------+-- --+-------+--
操作数栈 操作符栈
4) 读到 $y
+-------+
| $y |
+-------+ +-------+
| $x | | / |
+-------+ +-------+
| $i | | + |
--+-------+-- --+-------+--
操作数栈 操作符栈
5) 接着读到 -,由于 - 的运算符比 / 低,故取出 / 并从操作数栈中取出相应的操作数进行
计算,由于 / 是双目运算,故取出 $y, $x 进行 / 运算,然后将 - 存符符号栈,并接着
读取到 $j
+-------+
| $j |
+-------+ +-------+
| $x/$y | | - |
+-------+ +-------+
| $i | | + |
--+-------+-- --+-------+--
操作数栈 操作符栈
6) 由于操作符栈中还有未运算的符号,故每取出一个运算符同时从操作数栈中取出相应的数进
行运算,直到操作符栈为空。完成表达式计算时,操作数栈中的内容即为表达式的结果。懒
得再画图了,麻烦
6.1) 取出 -,双目运算 $x/$y - $j
6.2) 取出 +,双目运算 $i + $x/$y - $j
__________________________________________________________________________________
再回到 PHP中来,我们看两个例子,之前在 geel 及我发的 Quiz 中的例子, :p
1) $i = 2; echo ($i++ + $i);
1.1) 根据上面的基础知识得到以下栈图
+------+ +------+
| $i | | ++ |
--+------+-- --+------+--
1.2) 接着读取到 + 号,发现 + 号的优先级比 ++ 低,故先运算 ++ 再将 + 压入栈(单目运算)
+----------------------------+ +------+
| $i++(注:栈值=2,$i=3) | | + |
--+----------------------------+-- --+------+--
1.3) 读取到 $i 压入操作数栈 ($i = 3)
+----------+
| $i (=3) |
+----------+ +------+
| 2 | | + |
--+----------+-- --+------+--
1.2) 取出 + 作最后运算,至此,操作符栈已经全部完成,故运算完毕,操作数中的值就是结果
即 5.
+----------+
| 3 + 2 |
--+----------+-- --+------+--
2) $a = 1; $b =&$a; $b = $a++;
2.1) 仅从 ($b = $a++)出发,先得出如下栈图
+------+ +------+
| $b | | = |
--+------+-- --+------+--
2.2) 接着读取到操作数 $a
+------+
| $a |
+------+ +------+
| $b | | = |
--+------+-- --+------+--
2.3) 读到到操作符 ++ ,由于 ++ 的优先级比 = 高,故先取出 $a 做 ++ 运算
+----------------------+
| $a++ (栈值:1,$a=2) |
+----------------------+ +------+
| $b | | = |
--+----------------------+-- --+------+--
2.4) 读取 = 号作最后运算
+-----------+ +------+
| $b = 1 | | = |
--+-----------+-- --+------+--
2.5) 由于 $b 和 $a 互为关联,$a, $b的值均为最后一次赋值($b=1)的值,所以 $a = $b = 1