CHAPTER_7 提高篇(1)——数据结构(1)
7.2栈和队列的应用情形—表达式计算
一般来说,我们将表达式分为三种:前缀表达式、中缀表达式、后缀表达式。我们日常中使用最多的是中缀表达式。这里不在赘述这三种表达式的定义,有需要的读者可以参考博客前缀、中缀、后缀表达式(逆波兰表达式) - chensongxian - 博客园 。
栈的一个非常典型的应用,就是制作简单计算器,在计算器读入我们输入的中缀表达式时,并不会直接进行计算,通常会将它转变为后缀表达式,然后在计算后缀表达式的值。
接下来我们通过一个简单的例题,来练习栈和队列的应用。
题目:
读入一个包含 + - * / 四种运算符和括号的非负整数计算表达式,计算该表达式的值。
输入格式:
测试用例包含若干测试用例,每个测试用例占一行,每行不超过200个字符,整数和运算符之间用空格分隔,括号和数字之间不用空格分隔。括号皆是英文输入法下的括号,没有非法表达式。当一行中只有0时输入结束,相应的结果不要输出。
输出格式:
对每个测试用例输出一行,即该表达式的值,精确到小数点后2位。
输入样例:
30 / 90 - 26 + 97 - 5 - 6 - (13 / 88 * 6 + 51 / 29) + 79 * 87 + 57 - 92
985211 * 985 / 211
2 * (3 + 5 / 2)
0
输出样例:
12178.21
4599207.75
11.00
思路:
我们用string来接收输入的表达式。首先我们要对输入字符串进行去空格操作,以便于后续的处理。而处理的方法也很简单,我们遍历串,对出现空格的位置使用erase()方法即可。
接下来的算法包含两个步骤:
(1)中缀表达式转后缀表达式;
(2)计算后缀表达式。
步骤一:中缀转后缀-void change()函数
设立一个栈用于临时存放操作符;设立一个队列,用以存放后缀表达式。接着我们要按照如下定义运算符的优先级:乘号=除号>加号=减号,具体的实现可以用哈希表。然后我们从左至右开始遍历输入字符串,对于每一次遍历,按照如下情况处理:
(1)如果当前字符为数字,我们要考虑到数字可能不止一位,因此需要继续向后面遍历,将碰到的数字一位位合并,直到碰到操作符停止,将最后所得的操作数入队。例如123+4中,我们要将遍历得到的'1' '2' '3'合并成123。最后遍历继续至字符串下一位。
(2)如果当前字符为操作符op,比较其栈顶运算符的优先级。
a. op为左括号,则将op入栈,遍历继续至字符串下一位;
b. 若栈为空或者op的优先级大于栈顶元素的优先级,则将op入栈,遍历继续至字符串下一 位;
c. 若op的优先级小于等于栈顶元素,则将栈顶操作符出栈,并将其入队存放于后缀表达式; 之后我们的遍历停止一位,也就是继续保持当前操作符号op,待下一轮继续比较op与栈顶 元素的优先级
d. 若op为右括号,依次将栈顶元素出栈,并且将它们入队,直达栈顶元素为左括号停止。最 后将栈顶左括号出栈丢弃,遍历继续至字符串下一位。
待字符串遍历完成后,将栈内所有剩余操作符依次出栈,然后入队。
最后按顺序遍历队列,即可得到转换后的后缀表达式。
步骤二:后缀表达式的计算-double cal()函数
从左至右遍历后缀表达式(实际上就是队列出队的过程),对每一次遍历,按照如下情况处理:
a. 若当前为数字,将其压入栈。
b. 若当前为运算符op,弹出栈顶的两个数a和b,计算b op a,并将结果入栈;
待后缀表达式遍历完成后,栈内剩余唯一元素(即为栈顶元素),该元素为最终结果。
PS:上面描述了算法过程,而并未阐述其原理。读者不妨跟着参考博客中的举例来手动模拟这个算法,详细体会其过程。进而在这个过程中,慢慢理解其原理。前缀、中缀、后缀表达式(逆波兰表达式) - chensongxian - 博客园 (cnblogs.com) 。
代码如下:
#include <iostream>
#include <string>
#include <map>
#include <stack>
#include <queue>
using namespace std;
typedef struct Node {
	double num;       //操作数 
	char op;          //操作符 
	bool flag;        //true表示操作数,false表示操作符
}node;                //node是一个既能存放数字又能存放运算符的结构体,用flag来控制其存储哪种类型 
stack<node> s;
queue<node> q;
map<char,int> op;
string str;
void change() {
	node tmp;
	for(int i=0;i<str.size();) {
		if(str[i]>='0'&&str[i]<='9') {                       //当前为数字 
			tmp.flag=1;                                      //标记为数字 
			tmp.num=0;
			while(str[i]>='0'&&str[i]<='9'&&i<str.size()) {  //将数字逐一合并,直至碰到操作符或者遍历完串 
				tmp.num=tmp.num*10+(str[i]-'0');
				i++;
			}
			q.push(tmp);                                     //该数字入队 
		}
		else if(str[i]=='(') {                //左括号则入栈
			tmp.flag=0; 
			tmp.op='(';
			s.push(tmp);
			i++;
		}
		else if(str[i]==')') {               //当前为右括号,持续将栈顶出栈入队,直到碰到左括号 
			while(s.top().op!='(') {
				q.push(s.top());         //栈顶元素入队 
				s.pop();                 //出栈
			}
			s.pop();                     //丢弃栈顶左括号 
			i++;
		}
		else if(s.empty()||op[str[i]]>op[s.top().op]) {        //栈顶优先级低或者栈空 
			tmp.flag=0;
			tmp.op=str[i];               //运算符入栈 
			s.push(tmp);
			i++;
		}
		else {                           //栈顶优先级大于等于操作符 
			q.push(s.top());
			s.pop();                     //栈顶入队,然后出栈, 
		}
	}
	while(!s.empty()) {                      //遍历完成后,将栈内剩余操作符入队 
		q.push(s.top());
		s.pop();
	}
}
double cal() {
	double a,b;
	char tmp_op;
	node tmp;
	while(!q.empty()) {
		if(q.front().flag==true) {           //当前为数字 
			s.push(q.front());
			q.pop();                         //将队首数字压入栈,然后出队 
		}
		else {                               //当前为操作符 
			tmp_op=q.front().op;
			q.pop();                         //队首操作符赋予临时变量 
			a=s.top().num;
			s.pop();
			b=s.top().num;
			s.pop();                         //弹出栈顶两个数字,并赋予a和b
			switch(tmp_op) {                 //根据运算符做不同运算 
				case '+':
					tmp.num=b+a;
					break;
				case '-':
					tmp.num=b-a;
					break;
				case '*':
					tmp.num=b*a;
					break;
				case '/':
					tmp.num=b/a;
					break;	
			}
			tmp.flag=1;
			s.push(tmp);                     //将结果入栈 
		}
	}
	return s.top().num;
}
int main() {
	op['*']=2;
	op['/']=2;
	op['+']=1;
	op['-']=1;
	op['(']=0;                            //定义运算符优先级 
	while(getline(cin,str),str!="0") {
		for(string::iterator it=str.begin();it!=str.end();it++) {
			if(*it==' ')
				str.erase(it);
		}                                 //去空格
		while(!s.empty()) {
			s.pop();
		}                                //将栈初始化为空 
		change();
		printf("%.2f\n",cal());
	return 0;
	}
} 
                
中缀表达式到后缀表达式转换及计算
        
                  
                  
                  
                  
                            
      
          
                
                
                
                
              
                
                
                
                
                
              
                
                
              
            
                  
					238
					
被折叠的  条评论
		 为什么被折叠?
		 
		 
		
    
  
    
  
            


            