《算法笔记》读书记录DAY_25

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值