课程设计题目:表达式求值
一、问题描述与基本要求
利用栈,实现以下功能:
- 中缀表达式求值;
- 中缀表达式转后缀表达式;
- 后缀表达式求值。
假设用户输入的表达式均合法,且运算符只含+
(加号)、-
(减号)、*
、/
、%
、(
和)
,允许浮点数(浮点数计算取模规定为向零取整后得到整数再计算取模)。
二、概要设计
1. 数据结构的设计
我们利用栈来实现上述3个功能。具体原因见后文算法的设计
。
2. 算法的设计
2.1 表达式读入与预处理
用户从键盘输入一个语法正确的中缀/后缀表达式,但格式可能并不标准,例如有多余的空格。另外,为了在后面计算表达式编码更方便,我想利用C++中的istringstream
,所以要将表达式存储在字符串中,且数与数、数与符号、符号与符号之间都有空格,最后在字符串末尾加上#
(原因见下)。
用length表示输入的中缀表达式字符串长度,则
时间复杂度 | 空间复杂度 |
---|---|
O ( l e n g t h ) O(length) O(length) | O ( l e n g t h ) O(length) O(length) |
2.2 计算中缀表达式
先定义运算符的优先级。注意运算符在栈内和栈外的优先级是不同的,这是为了实现同优先级能从左往右计算。
运算符 | + - | * / % | ( | ) | # ; |
---|---|---|---|---|---|
isp(栈内优先级) | 3 | 5 | 1 | 6 | 0 |
icp(栈外优先级) | 2 | 4 | 6 | 1 | 0 |
定义#
或;
,是为了后面统一处理表达式求值。
接下来,我们定义两个栈:数值栈(num_stack)和符号栈(sgn_stack)。
从左往右扫描中缀表达式,如果:
- 遇到数值就直接将它压入数值栈;
- 遇到运算符(记当前运算符为cur),检查符号栈的栈顶运算符的优先级
- 栈顶优先级小于cur,则直接压入cur
- 栈顶优先级大于cur,则弹出栈顶运算符和数值栈的两个数来计算,再把计算结果压入数值栈。重复直到栈顶运算符的优先级不再大于cur,再将cur压入。
- 栈顶优先级等于cur,从上面的运算符优先级的表格中我们可以知道这时候栈顶是左括号,cur是右括号,那么直接将这两个括号扔掉就好了。
- 最后数值栈里就只剩下一个数,就是整个中缀表达式的计算结果
如果我们不在原本的中缀表达式后面加#
或;
,那么还需要把符号栈中的运算符依次弹出来并计算,这无疑让编码更加繁琐更易出错。所以,不如在后面加上#
或;
,并把优先级设计成最低(0),统一处理。
伪代码如下
for (从左到右扫描中缀表达式)
if (cur == 数值)
num_stack.push(cur)
else // cur == 运算符
while (isp(sgn_stack.top()) > icp(cur))
b = num_stack.top(), num_stack.pop()
a = num_stack.top(), num_stack.pop()
用 sgn_stack.top() 计算 a 和 b,结果存到 c
num_stack.push(c)
sgn_stack.push(cur)
用n表示输入的中缀表达式中运算符个数,则
时间复杂度 | 空间复杂度 |
---|---|
O ( n ) O(n) O(n) | O ( n ) O(n) O(n) |
2.3 中缀表达式转后缀表达式
我们只需要一个符号栈(sgn_stack)就够了。
从左往右扫描中缀表达式,如果:
- 遇到数值就直接将它输出;
- 遇到运算符(记当前运算符为cur),检查符号栈的栈顶运算符的优先级
- 栈顶优先级小于cur,则直接压入cur
- 栈顶优先级大于cur,则弹出栈顶运算符并输出。重复直到栈顶运算符的优先级不再大于cur,再将cur压入。
- 栈顶优先级等于cur,从上面的运算符优先级的表格中我们可以知道这时候栈顶是左括号,cur是右括号,那么直接将这两个括号扔掉就好了,因为后缀表达式不需要括号。
同样,这里在原来的中缀表达式后加上#
或;
会更方便。
后缀表达式并不需要在最后面也加上#
或;
(因为它的计算和编码已经够简单了)。当然,加上也没问题。
伪代码如下
for (从左到右扫描中缀表达式)
if (cur == 数值)
输出 cur
else // cur == 运算符
while (isp(sgn_stack.top()) > icp(cur))
输出 sgn_stack.top() sgn_stack.pop()
sgn_stack.push(cur)
用length表示输入的中缀表达式字符串长度,则
时间复杂度 | 空间复杂度 |
---|---|
O ( l e n g t h ) O(length) O(length) | O ( l e n g t h ) O(length) O(length) |
2.4 计算后缀表达式
后缀表达式的计算就简单了:遇数压栈,遇符就弹两个数来算。
for (从左到右扫描后缀表达式)
if (cur == 数值)
num_stack