我们平时书写的算术表达式都是中序表达式,例如
- (1 + 2) x 3/4 - 5
而对应的后缀表达式就是
- 12+3x4/5-
如何由中序表达式变为后缀表达式呢?具体做法如下:
- 建立符号栈s
- 从左向右扫描中序表达式
- 碰到数字,直接输出
- 碰到运算符
- 如果s为空或者栈顶元素为左括号,则压入s,回到从左向右扫描中序表达式
- 运算符的优先级大于s栈顶运算符的优先级,压入s,回到从左向右扫描中序表达式
- 运算符的优先级小于或者等于s栈顶运算符的优先级,弹出并输出s栈顶元素,回到碰到运算符
- 如果是左括号,压入s
- 如果是右括号
- 弹出s栈顶元素
- 如果弹出的栈顶元素是左括号,则不输出,回到从左向右扫描中序表达式
- 如果弹出的栈顶元素不是左括号,则输出,回到弹出s栈顶元素
- 弹出s栈顶元素
- 弹出并输出s中的符号
所以对于 (1+2)×3/4−5 可以这样去做
建立符号栈s
输出: s:
从左向右扫描,第一个左括号,压入s
输出: s:(
1,输出
输出:1 s:(
+,栈顶元素为左括号,压入s
输出:1 s:(+
2,输出
输出:12 s:(+
右括号,弹出栈顶元素+并输出
输出:12+ s:(
栈顶元素是左括号
输出:12+ s:
x,压入s
输出:12+ s:x
3,输出
输出:12+3 s:x
/,优先级等于栈顶元素优先级,弹出并输出栈顶元素
输出:12+3x s:
栈为空,压入s
输出:12+3x s:/
4,输出
输出:12+3x4 s:/
-,优先级小于栈顶元素优先级,弹出并输出栈顶元素
输出:12+3x4/ s:
栈为空,压入s
输出:12+3x4/ s:-
5,输出
输出:12+3x4/5 s:-
弹出并输出s
输出:12+3x4/5- s:
关于后缀表达式的计算,就是所有的计算按运算符出现的顺序,严格从左向右进行即可。但是我们是不是每次都要先将中缀转化为后缀在计算呢?如何按后缀表达式方式的思想计算中序表达式呢?具体做法如下:
- 建立数字栈s1,符号栈s2
- 表达式不为空,从左向右扫描中序表达式
- 碰到数字压入s1
- 碰到操作符
- 如果s2为空或栈顶元素为左括号,则压入s2,回到从左向右扫描中序表达式
- 如果s2不空,且运算符的优先级和s2栈顶的运算符优先级高,则将这个运算符压入s2 ,回到从左向右扫描中序表达式
- 如果s2不空,且运算符的优先级比s2栈顶的运算符优先级相同或比其低,则从s1 中弹出两个数字与s1栈顶弹出的运算符做运算,将结果压入s1,然后回到碰到操作符。
- 碰到左括号,则压入s2
- 碰到右括号
- 如果s2栈顶元素不是左括号,则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入s1
- 如果s2栈顶元素是左括号,弹出左括号
- 表达式为空,如果s2不为空,则从s1 中弹出两个数字与s2栈顶弹出的运算符做运算,将结果压入s1,直到s2为空
- 如果s1只有一个,ok,否则肯定就出错了^_^!
对于 (1+2)×3/4−5 这样计算
建立s1,s2
s1: s2:
左括号,压入s2
s1: s2:(
1,压入s1
s1:1 s2:(
+,s2为空,压入s2
s1:1 s2:(+
2,压入s1
s1:12 s2:(+
右括号,s2栈顶元素不是左括号,则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入s1
s1:3 s2:(
s2栈顶元素是左括号,弹出左括号
s1:3 s2:
x,s2为空,压入s2
s1:3 s2:x
3,压入s1
s1:33 s2:x
/,s2不空,且运算符的优先级和s2栈顶的运算符优先级相同,则将这个运算符压入s2
s1:33 s2:x/
4,压入s1
s1:334 s2:x/
-,s2不空,且运算符的优先级比s2栈顶的运算符优先级相同或比其低,则从s1 中弹出两个数字与s1栈顶弹出的运算符做运算,将结果压入s1
s1:3 0.75 s2:x
s2不空,且运算符的优先级比s2栈顶的运算符优先级相同或比其低,则从s1 中弹出两个数字与s1栈顶弹出的运算符做运算,将结果压入s1
s1:2.25 s2:
s2为空,压入s2
s1:2.25 s2:-
5,压入s1
s1:2.25 5 s2:-
s2不为空,则从s1 中弹出两个数字与s2栈顶弹出的运算符做运算,将结果压入s1
s1:-2.75 s2:
最后一个问题就是关于负数和浮点数的问题,关于这方面的处理我放在了代码中。负数的问题,主要分为减号语意和负数语意去处理。我觉得这里最简单的方式,就是对于负号前面空或者是左括号,我们给它插上个0,这样问题就归为了减号语意一个问题。当然分两个去做也没有问题,只是讨论情况会很麻烦。
/**********************************************************
*2017_10_2 by coordinte
*前缀四则运算
*win10 vs2015 x86 debug
*www.coordinate.wang
************************************************************/
#include <iostream>
#include <stack> //内部是通过deque实现的
#include <string>
#include <map>
#include <cctype> //isdigit
#include <cstdlib> //stod
#include <stdexcept>
#include <algorithm> //remove
using std::string;
//运算符的优先级
std::map<char, int> priority{ { '+', 1 },
{ '-', 1 },
{ '*', 2 },
{ '/', 2 } };
//计算两个数的值,返回结果
double execute(std::stack<char>& ops, std::stack<double>& operands)
{
double result{};
double rhs{ operands.top() }; operands.pop();
double lhs{ operands.top() }; operands.pop();
switch (ops.top())
{
case '+':
result = lhs + rhs;
break;
case '-':
result = lhs - rhs;
break;
case '*':
result = lhs * rhs;
break;
case '/':
result = lhs / rhs;
break;
default:
throw std::runtime_error{ string{ "invalid operator: " + ops.top() } };
}
ops.pop();
operands.push(result);
return result;
}
//将输入字符串转化为前缀形式
double toSuffix(string str_in)
{
std::stack<char> operators;
std::stack<double> operands;
size_t index{}; //标记字符串中数字的下一个字符的位置
size_t i{}; //标记字符的位置www.coordinate.wang
while (i < str_in.length())
{
char s_i = str_in[i];
if (s_i == '(')
{
operators.push(s_i);
++i;
}
else if (s_i == ')')
{
char temp{};
while ((temp = operators.top()) != '(')
{
execute(operators, operands);
if (operators.empty())
{
return -1;
}
}
//去除temp
operators.pop();
++i;
}
//处理操作符
else if (priority.count(s_i))
{
while (!operators.empty() && operators.top() != '('
&& priority.at(s_i) <= priority.at(operators.top()))
{
execute(operators, operands);
}
operators.push(s_i);
++i;
}
else//处理数字
{
double number = std::stod(str_in.substr(i), &index);
operands.push(number);
i += index;
}
}
while (!operators.empty())
{
execute(operators, operands);
}
double result = operands.top(); operands.pop();
if (!operands.empty())
return -1;
return result;
}
//输入字符串的小数点前要有数字,如0.1,不可以是.1
int main()
{
string str_in;
std::cout << "An arithmetic can include the operators +,-,*,/." << std::endl;
try
{
while (true)
{
std::cout << "Enter an arithmetic expression and press Enter"
<< " - enter an empty line to end:"
<< std::endl;
std::getline(std::cin, str_in, '\n');
if (str_in.empty()) break;
//删除空格
str_in.erase(std::remove(std::begin(str_in), std::end(str_in), ' '), std::end(str_in));
//如果负号前面是空或者左括号的话插入0
for (size_t i = 0; i < str_in.length(); ++i)
{
if (str_in[i] == '-' && i == 0)
{
str_in.insert(0, 1, '0');
}
else if (str_in[i] == '-' && str_in[i - 1] == '(')
{
str_in.insert(i, 1, '0');
}
}
std::cout << toSuffix(str_in) << std::endl;
}
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
std::cout << "Calculator ending ..." << std::endl;
system("pause");
}
这个程序稍加改动就可以输出后缀表达式了。不再赘述,留给读者思考。