计算器

计算器



题目描述

做一个计算器,能够实现四则运算,且满足括号的使用


一、分析

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;
}

总结

直接采用中缀表达式的方法求解可能时初学者的第一反应,但是这种方法需要考虑的情况太多,过于复杂,实现起来也很困难,且本文多次使用字符串流输入,时间成本和空间成本都较大,而逆波兰表达式则不需要考虑先后顺序,相对而言后者的算法更优。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值