计算器
题目描述
做一个计算器,能够实现四则运算,且满足括号的使用
一、分析
1.中缀表达式
中缀表达式:
即我们通常所列的算式,例如:
a - b + c * ( d + e ) - f
思路
作为初学者,我首先想到是直接利用中缀表达式直接进行求解,大致思路如下:
首先定义了三个数字sum,sub_sum,num,sum用来表示总和,sub_sum用来表示部分和,num则时用来表示读取到的最后面的数,之所以用第三个数同时遍历整个表达式,是因为当遇到a+b*c的情况时,可以让sum保存a的值,sub_sum能够短暂地保留b的值,使b与后面的数做后面优先级更高的运算并赋值给sub_sum,再返回来做加法运算,即让sub_sum的值赋给sum。
当遇到括号时,我让括号部分返回一个整体值,供主函数使用,在其内部则利用函数递归调用实现多重括号的运算。
2.后缀表达式
后缀表达式
又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不需要考虑运算符的优先规则)。例如:
a - b + c * ( d + e ) - f
可转化为
a b - c d e + * + f -
运算过程:
(1)从左至右遍历,将 a 和 b 压入堆栈;
(2)遇到 - 运算符,因此弹出 b 和 a( b 为栈顶元素,a 为次顶元素,即栈的顶部两个元素),计算出 a-b 的值,再将 a-b 入栈;
(3)将 c d e入栈;
(4)接下来是 + * + 运算符,因此弹出 e 和 d,先将 d+e 入栈,然后弹出 d+e 和c,将 c×(d+e) 入栈,然后计算 (a-b)+c×(d+e);
(6)将 f 入栈;
(7)最后是-运算符,得出最终结果。
思路
毫无疑问,肯定先将输入的中缀表达式转化为后缀表达式。在遍历过程中,一共会出现4种情况:
(1)当前字符为前括号时,直接进符号栈;
(2)当前字符为操作符时,判断操作符优先度,若该字符的优先度大于栈顶元素的优先度,则直接入栈,否则取出栈顶元素,直到栈顶元素为前括号或者优先度大于栈顶元素或者栈空,而取出的元素可以用作计算;
(3)当前字符为后括号时,该元素不入栈,而是取出符号栈中的元素一一取出,用作计算,直到遇到前括号;
(4)当前字符为数字时,获取该字符所在的 整个数字,并进入数字栈。
代码如下:
void exchange_arr(){
int len=str.length();
for(int i=0;i<len;i++){
if(str[i]=='('){
op.push(str[i]);
}else if(str[i]==')'){
while(op.top()!='('){
char tmp=op.top();
op.pop();
calculate(tmp);
}
op.pop();
}else if(judge(str[i])){
while(!op.empty() && pri(str[i])<=pri(op.top())){
char tmp=op.top();
op.pop();
calculate(tmp);
}//优先级比栈内小时,需要将栈内操作符弹出
op.push(str[i]);
}else if(str[i]<='9' && str[i]>='0'){
int num=0;
while(str[i]>='0' && str[i]<='9' && i<len){
num=num*10+str[i++]-'0';
}
i--;
output.push(num);
}
}
}
二、代码实现
1.中缀表达式
这里采用了字符串流输入输出,可以直接得到数字,代码如下(如有缺陷,望指正):
#include<iostream>
#include<sstream>
#include<cstring>
using namespace std;
string str;//用来接受用户输入的字符串
stringstream ssin[1000];//字符串流
int k=0;//表示第k号字符串流
bool judge(char ch);//判断四则运算的符号,乘除为1,加减为0
int cal(int a,int b,char c);//数字a和数字b进行c符号运算
void cal2();//计算括号内的值,具体实现与主函数类似
void judge2(int num);//每次用流输入数据之前,判断是否遇到括号
int main(){
cin>>str;
ssin[k]<<str<<"=";//用等号作为主程序中结束的标志
int sum=0,sub_sum,num;//sum->和 sub_sum->字段和 num->最新输入的数字
char c='+', c1, c2;//sum sub_sum num后面的符号
judge2(sub_sum);
ssin[k]>>sub_sum>>c1;
judge2(num);
while(ssin[k]>>num>>c2){
if(c2==')') ssin[k]>>c2;
if(c2=='='){
sub_sum=cal(sub_sum,num,c1);
sum=cal(sum,sub_sum,c);
break;
}
if(judge(c1) || (!judge(c1) && !judge(c2))){
//sub_sum和num可直接运算时
sub_sum=cal(sub_sum,num,c1);
c1=c2;
}else{
//num需要先与后面数据运算时
sum=cal(sum,sub_sum,c);
c=c1;sub_sum=num;c1=c2;
judge2(num);
ssin[k]>>num>>c2;
if(c2==')') ssin[k]>>c2;
sub_sum=cal(sub_sum,num,c1);
c1=c2;
if(c2=='='){
ssin[k]<<num<<c2;
}
}
judge2(num);
}
cout<<sum;
return 0;
}
void cal2(){
int sum=0,sub_sum,num;
char c='+', c1, c2;
judge2(sub_sum);
ssin[k]>>sub_sum>>c1;
judge2(num);
while(ssin[k]>>num>>c2){
if(c2==')'){
sub_sum=cal(sub_sum,num,c1);
sum=cal(sum,sub_sum,c);
string tmp_str;
ssin[k]>>tmp_str;
ssin[++k]<<sum<<tmp_str;//使sum进入stream中,保证之后一定会读取到一个数字
break;
}
if(judge(c1) || (!judge(c1) && !judge(c2))){
sub_sum=cal(sub_sum,num,c1);
c1=c2;
}else{
sum=cal(sum,sub_sum,c);
c=c1;sub_sum=num;;c1=c2;
judge2(num);
ssin[k]>>num>>c2;
if(c2==')'){
sub_sum=cal(sub_sum,num,c1);
sum=cal(sum,sub_sum,c);
string tmp_str;
ssin[k]>>tmp_str;
ssin[++k]<<sum<<tmp_str;
break;
}
sub_sum=cal(sub_sum,num,c1);
c1=c2;
if(c2==')'){
ssin[k]<<num<<c2;
}
}
judge2(num);
}
}
void judge2(int num){
string tmp_str;
ssin[k]>>tmp_str;
if(tmp_str[0]=='('){
char *p=&tmp_str[1];
ssin[++k]<<p;
cal2();
}else{
ssin[++k]<<tmp_str;
}
}
bool judge(char ch){
bool res=true;
if(ch=='+' || ch=='-'){
res=false;
}
return res;
}
int cal(int a,int b,char c){
int res=a;
switch(c){
case '+':res=a+b;break;
case '-':res=a-b;break;
case '*':res=a*b;break;
case '/':res=a/b;break;
}
return res;
}
2.后缀表达式之栈处理
代码如下(示例):
#include<bits/stdc++.h>
using namespace std;
void exchange_arr(); //中缀转后缀
void calculate(char);//将数字栈中的两个元素进行计算
bool judge(char);//判断是否为四则运算
int pri(char);//判断优先级
string str;
stack<char> op;//运算符栈
stack<double> output;//数字栈
int main(){
cin>>str;
op.push('=');
output.push(0);
exchange_arr();
while(op.size()>1){
char tmp=op.top();
op.pop();
calculate(tmp);
}
cout<<"output = "<<endl;
cout<<" "<<output.top();
return 0;
}
void calculate(char ch){
int a,b;
b=output.top();
output.pop();
a=output.top();
output.pop();
double res=0;
switch(ch){
case '+':res=a+b;break;
case '-':res=a-b;break;
case '*':res=1.0*a*b;break;
case '/':res=1.0*a/b;break;
}
output.push(res);
}
int pri(char ch){
int res=0;
switch(ch){
case '(':
res=1;break;
case '+':case '-':
res=2;break;
case '*':case'/':
res=3;break;
case ')':
res=4;break;
}
return res;
}
bool judge(char ch){
if(ch=='+' || ch=='-' || ch=='*' || ch=='/'){
return true;
}
return false;
}
3.后缀表达式之二叉树处理
这里只提供利用二叉树中缀转后缀的代码,改代码来源于CSUOJ上的一个题的题解,可以为二叉树实现中缀转后缀提供帮助:
Description
对于二元表达式,我们可以对其建立一棵二叉树,,以基本运算对象作为叶结点中的数据,以运算符作为非叶节点中的数据,其两棵子树是它的运算对象,子树可以是基本运算对象,也可以是复杂表达式。如图就是一棵表达式树。对表达式树进行后序遍历(先遍历左子树,再遍历右子树,最后访问根节点)就可以得到逆波兰表达式。
现在小 Z 得到了若干棵表达式树,但他不会转换为逆波兰表达式并求值,所以他想求助于你,请你帮助他将这些表达式树转换为逆波兰表达式,并计算出其对应的值。
Input
第一行 T ,代表数据组数(不超过 100 组)
对于每组数据
首先一个 n ,代表表达式树的节点个数( 1≤n≤100000 )
接下来 n 行,每行 x , y , z ,第 i 行代表对第 i 个节点的描述,1 代表根节点
x=0 代表这个节点是运算符, y 是 “+”、“-”、“*”、“/” 四种运算符中的一个
x=1 代表这个节点是操作数, y 是其值, y∈[−1000,1000]
z 代表该节点的父亲编号,根节点的父亲编号为 0;
对于操作符的子树,输入中先出现的为其左子树,后出现的为其右子树。
数据保证运算中间过程和结果属于 [−231,231−1],且合法的除法均为整除
Output
对每一组数组,输出两行
第一行为其表达式树的逆波兰表达式(以空格分隔)
第二行为其对应的值,如果运算中出现除以0的非法情况,则输出“ERROR”(不含引号)
Sample Input
2
11
0 - 0
0+ 1
1 7 2
0 * 2
1 8 4
0 / 1
1 6 6
1 3 6
0 - 4
1 5 9
1 2 9
5
0 / 0
1 3 1
0 - 1
1 6 3
1 6 3
Sample Output
7 8 5 2 - * + 6 3 / -
29
3 6 6 - /
ERROR
核心代码部分如下:
struct node{
int val;
char ch;
node *left=NULL,*right=NULL;
};
int calu(node *p){
int res=0;
if(p->left!=NULL && p->right!=NULL){
int a=calu(p->left);
int b=calu(p->right);
printf("%c ",p->ch);
switch(p->ch){
case '+':res=a+b;break;
case '-':res=a-b;break;
case '*':res=a*b;break;
case '/':res=a/b;break;
}
}else{
printf("%d ",p->val);
res=p->val;
}
return res;
}
总结
直接采用中缀表达式的方法求解可能时初学者的第一反应,但是这种方法需要考虑的情况太多,过于复杂,实现起来也很困难,且本文多次使用字符串流输入,时间成本和空间成本都较大,而逆波兰表达式则不需要考虑先后顺序,相对而言后者的算法更优。