Sicily. 完整计算器(浮点数;四则运算与括号)

Time Limit: 1sec    Memory Limit:256MB
Description

 请实现一个只包含加减乘除以及括号运算的简单计算器,忽略式子中的所有空格,输入格式是正确的,不需判断。

Input

 第一行只有一个整数m,代表有m个测试用例
接下来有m行,每一行是一个测试用例,代表要求计算的式子,请注意输入的数字可能有小数。

 

Output

 对于每个测试用例,输出一行,每一行只有一个数,表示式子的运算结果,保留3位小数(四舍五入)

Sample Input
2
1+2
1/3
Sample Output
3.000
0.333


_________________________________________________________________________________________________________

略有难度,略有难度。


这道题的描述计算器和电脑自带的计算器有区别,电脑的不接收整一串表达式,这道题则需要。
这种表达式是人眼能看懂的,行话叫“中缀表达式”,而计算机能看得懂的是“后缀表达式”(又叫“逆波兰式”)。比如,中缀表达式(a+b)*c-(a+b)/e的逆波兰式是ab+c*ab+e/-

怎么由中缀表达式转成后缀表达式呢?手动操作其实不难,把中缀表达式画成一棵表达式二叉树(叶节点是操作数,内部节点是操作符,优先级越高的操作符在越深层)即可。而把这棵二叉树按后根(post-order)遍历,就恰好能得到它的后缀表达式。

比如中缀表达式(a+b)*c-(a+b)/e,画出二叉树,后根遍历得到逆波兰式ab+c*ab+e/-
表达式二叉树

等一下,为什么我们需要后缀表达式?
因为有了后缀表达式就简单了。引用维基的例子:

中缀表达式“5 + ((1 + 2) * 4) − 3”写作 5 1 2 + 4 * + 3 −

下表给出了该逆波兰表达式从左至右求值的过程,堆栈栏给出了中间值,用于跟踪算法。

输入 操作 堆栈 注释
5入栈5
1入栈5, 1
2入栈5, 1, 2
+加法运算5, 3(1, 2)出栈;将结果(3)入栈
4入栈5, 3, 4
*乘法运算5, 12(3, 4)出栈;将结果(12)入栈
+加法运算17(5, 12)出栈;将结果 (17)入栈
3入栈17, 3
减法运算14(17, 3)出栈;将结果(14)入栈

计算完成时,栈内只有一个操作数,这就是表达式的结果:14


接下来就是写代码了。难点在于将中缀表达式转换成后缀表达式。这位大神的文章很有用!
以下是我的代码,自认为注释比较详细:

#include <stdio.h>
#include <iostream>
#include <sstream>
#include <stack>

using namespace std;

  //  枚举类型:标记操作符和操作数
enum Attribute { OPERATOR, OPERAND };

  //  因为栈中要操作数、操作符混用,所以使用结构体,并用一个变量来标记是操作数还是操作符
struct node {
	double value;  //  操作数还是操作符的值
	Attribute attr;  //  标记是操作数还是操作符

	node() {}
	node(double v, Attribute a) : value(v), attr(a) {}
};

  //  对原始表达式进行预处理
void initialize(string str, string &exp);

  //  判断是否为操作符
bool isOperator(char a);

  //  将人能读的算数表达式转换成逆波兰式(中缀表达式==>后缀表达式),生成栈result(栈顶是逆波兰式首部)
void buildPolish(string exp, stack<node>& result);

  //  辅助函数,从calculation栈中取出2个操作数,进行相应的计算并返回结果
void calculate(stack<double>& calculation, char sign);

  //  将逆波兰式计算出最终结果
double calPolish(stack<node> polish);

int main() {
	int T;  //  测试样例数
	string str, exp;  //  str是原始表达式,exp则是经过预处理后得到的表达式

	scanf("%d", &T);
	getline(cin, str);  //  若不这样先写一句getline,那么第一个getline结果是空字符串
	while (T--) {
		getline(cin, str);  //  输入原始表达式

		//  对原始表达式进行预处理
		initialize(str, exp);

		//  用于顺序存储逆波兰式的栈
		stack<node> polish;

		//  算数表达式转换成逆波兰式
		buildPolish(exp, polish);

		//  计算逆波兰式的算数结果
		double answer = calPolish(polish);

		printf("%.3lf\n", answer);
	}

	return 0;
}

  //  对原始表达式进行预处理
void initialize(string str, string &exp) {
	exp = "";

	//  去除原表达式中的空格
	//  同时,如果左括号的右边是'+'或'-',则在二者之间加入数字0,
	//  从而强制地将'+'或'-'从操作数的正或负号变为加或减运算符
	for (int i = 0; i < str.length(); i++) {
		if (str[i] != ' ') {
			exp += str[i];
		}
		if (str[i] == '(' && (str[i + 1] == '+' || str[i + 1] == '-')) {
			exp += '0';
		}
	}

	//  若表达式以'+'或'-'开头,则在开头处加入数字0
	//  同样要强制地将'+'或'-'从操作数的正或负号变为加或减运算符
	if (exp[0] == '+' || exp[0] == '-')
		exp = "0" + exp;
}

  //  判断是否为操作符
bool isOperator(char a) {
	return a == '(' || a == ')' || a == '+' || a == '-' || a == '*' || a == '/';
}

  //  将人能读的算数表达式转换成逆波兰式(中缀表达式==>后缀表达式),生成栈result(栈顶是逆波兰式首部)
void buildPolish(string exp, stack<node>& result) {
	stack<node> s1;  //  临时存储运算符(含一个结束符号#),越往栈顶符号优先级越高
	stack<node> s2;  //  用于输入逆波兰式
	istringstream stream(exp);  //  用于读操作数和操作符
	double num;  //  读入的操作数
	char ch;  //  读入的操作符

	//  s1中先放入结束符#
	s1.push(node('#', OPERATOR));

	for (int i = 0; i < exp.size(); i++) {
		if (isOperator(exp[i])) {  //  发现是操作符
			stream >> ch;  //  stream向前走一位

			//  遇'('则直接入栈s1
			if (exp[i] == '(') {
				s1.push(node(exp[i], OPERATOR));
			}
			//  遇')'则将距离栈s1栈顶的最近的'('之间的运算符,逐个出栈,并依次送入栈s2。最后s1抛弃'('
			else if (exp[i] == ')') {
				while (s1.top().value != '(') {
					s2.push(s1.top());
					s1.pop();
				}
				s1.pop();
			}
			//  遇下列运算符,则分情况讨论:
			//  1.若当前栈s1的栈顶元素是'(',则当前运算符直接压入栈s1;
			//  2.否则,将当前运算符与栈s1的栈顶元素比较:若优先级较栈顶元素大,则直接压入栈s1中;
			//          否则将s1栈顶元素弹出,并压入栈s2中,
			//	直到栈顶运算符的优先级别严格低于当前运算符,然后再将当前运算符压入栈s1中
			else if (exp[i] == '+' || exp[i] == '-') {
				char c = s1.top().value;
				while (c != '#') {
					if (c == '(') {
						break;
					}
					else {
						s2.push(s1.top());
						s1.pop();
					}
					c = s1.top().value;
				}
				s1.push(node(exp[i], OPERATOR));
			}
			else if (exp[i] == '*' || exp[i] == '/') {
				char c = s1.top().value;
				while (c != '#' && c != '+' && c != '-') {
					if (c == '(') {
						break;
					}
					else {
						s2.push(s1.top());
						s1.pop();
					}
					c = s1.top().value;
				}
				s1.push(node(exp[i], OPERATOR));
			}
		}
		//  发现是操作数
		else {
			stream >> num;  //  用stream读出
			s2.push(node(num, OPERAND));  //  直接压入s2

			//  表达式字符串的下标i连续前进若干位直到碰见操作符
			//  【注】无法处理形如1e-4的用科学计数法表示的数,
			//  因为本解法中'+'或'-'都被当做加减号处理,而非数字的一部分
			while (!isOperator(exp[i + 1]) && exp[i + 1] != '\0') {
				i++;
			}
		}
	}

	//  若栈s1非空,则将栈中元素依次弹出并压入栈s2中
	while (!s1.empty() && s1.top().value != '#') {
		s2.push(s1.top());
		s1.pop();
	}

	//  此时s2中已经是逆波兰式,只不过式子的首部在栈底。所以把s2翻转过来,用另外一个栈存储
	while (!s2.empty()) {
		result.push(s2.top());
		s2.pop();
	}
}

  //  辅助函数,从calculation栈中取出2个操作数,进行相应的计算并返回结果
  //  注意操作数顺序!先出栈的操作数是减数(除数),后出战的操作数是被减数(被除数)
void calculate(stack<double>& calculation, char sign) {
	double op1 = calculation.top();
	calculation.pop();
	double op2 = calculation.top();
	calculation.pop();

	switch (sign) {
		case '+':
			calculation.push(op2 + op1); break;
		case '-':
			calculation.push(op2 - op1); break;
		case '*':
			calculation.push(op2 * op1); break;
		case '/':
			calculation.push(op2 / op1); break;
	}
}

  //  将逆波兰式计算出最终结果
double calPolish(stack<node> polish) {
	//  用一个栈存储临时结果
	stack<double> calculation;

	while (!polish.empty()) {
		//  如果逆波兰式的当前元素是操作数,则直接压进calculation栈
		if (polish.top().attr == OPERAND) {
			calculation.push(polish.top().value);
		}
		//  如果逆波兰式的当前元素是操作符,则从calculation栈中取出2个操作数,进行相应计算
		//  之后把计算结果压进calculation栈
		else if (polish.top().attr == OPERATOR){
			calculate(calculation, char(polish.top().value));
		}
		polish.pop();
	}

	//  最后,calculation栈顶的元素一定是最终结果
	return calculation.top();
}





  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值