中缀 后缀表达式 四则运算

中缀表达式转化为后缀表达式,并通过后缀表达式计算值

中缀表达式转化为后缀表达式

转化规则:

  • 设立一个操作符栈,用来存储操作符;设置一个数组或者队列用来存储后缀表达式(此处使用队列);

  • 从左到右扫描中缀表达式

    • 遇操作数直接加入到后缀表达式(此处即加入到队列末或数组末尾)

      注意一个操作数可能是多位,需要作为一个整体读入,例如 50 需要作为一个整体读入

    • 遇操作符 op

      • 入栈

        • op 优先级高于栈顶优先级

        • op 为 ( 直接入栈;

      • 出栈(出栈后元素添加到后缀表达式的末尾)

        • 若 op 优先级低于或等于栈顶优先级依次出栈直到栈顶优先级高于 op,然后 op 入栈;
        • op 为 ) 则出栈一直遇到第一个 (,将 ( 出栈后停止出栈;
  • 中缀表达式扫描完毕后,若栈内元素不为空,依次出栈直到栈为空,出栈元素添加到后缀表达式后

举例:3 * (2 + 5) - 4 / 2 过程如下

  1. 建立操作符栈,以及存储后缀表达式的队列:

    中缀 后缀表达式 四则运算

    中缀 后缀表达式 四则运算

  2. 从头到尾扫描中缀表达式,首先扫描到 3,为操作数,添加到队列尾,作为后缀表达式;

  3. 然后扫描到 * 操作符,此时栈顶为空,即 op 优先级大于栈顶,入栈;

  4. 扫描到 ( ,入栈;

  5. 扫描到 2,为操作数,添加到队列尾;

  6. 扫描到 +,为操作符,栈顶元素为 (,无优先级关系,入栈;

  7. 扫描到 5,为操作数,添加到队尾;

    2~7 过程生成的图如下:

    中缀 后缀表达式 四则运算

    中缀 后缀表达式 四则运算

  8. 扫描到 ),出栈一直到 ( 也出栈为止,出栈元素(除 ( 外)添加到队列尾;完成后如下图:

    中缀 后缀表达式 四则运算

    中缀 后缀表达式 四则运算

  9. 继续扫描中缀表达式,扫描到 -,优先级低于栈顶(*);出栈一直到栈顶优先级高于 - 或栈为空时,出栈停止,- 入栈;注意所有出栈元素依次添加到队列尾;如下图:

    中缀 后缀表达式 四则运算

    中缀 后缀表达式 四则运算

  10. 扫描到 4,添加到队尾;

  11. 扫描到 /,优先级大于栈顶(-),入栈;

  12. 扫描到 2,添加到队尾;

    10~12 过程生成图如下:

    中缀 后缀表达式 四则运算

    中缀 后缀表达式 四则运算

  13. 至此,中缀表达式扫描完毕;将栈内元素依次出栈添加到队尾;完成后栈为空,队列如下:

    中缀 后缀表达式 四则运算

  14. 队列元素依次出队输出可得后缀表达式:3 2 5 + * 4 2 / -

后缀表达式生成后,将对后缀表达式进行计算;

计算后缀表达式

计算规则:

  • 从左向右扫描后缀表达式;
  • 遇操作数,入栈;
  • 遇到操作符 op,存放操作数的栈依次出栈,出栈顺序为 top1 top2,对其做运算:top2 op top1;得到结果后将结果入栈;
  • 扫描完毕后,栈内只剩一个元素,出栈,即为后缀表达式的值;

以上面得出的后缀表达式 3 2 5 + * 4 2 / - 为例,计算过程如下:

  1. 扫描得到 3,为操作数,入栈;

  2. 扫描得到 2,为操作数,入栈;

  3. 扫描得到 5,为操作数,入栈;

    1~3 过程结果如下:

    中缀 后缀表达式 四则运算

  4. 扫描到 +,为操作符,依次出栈两次,出栈顺序为 5(top1) 2(top2);使用操作符运算:2 + 5

    结果为 7 入栈;

    中缀 后缀表达式 四则运算

  5. 扫描到 *,依次出栈两次,出栈顺序为 7(top1) 3(top2);使用操作符运算:3 * 7

    结果为 21 入栈;

    中缀 后缀表达式 四则运算

  6. 扫描到 4 为操作数,入栈;

  7. 扫描到 2 为操作数,入栈;

    中缀 后缀表达式 四则运算

  8. 扫描到 /,依次出栈两次,出栈顺序为 2(top1) 4(top2);使用操作符运算:4 / 2

    结果为 2 入栈;

    中缀 后缀表达式 四则运算

  9. 扫描到 -,依次出栈两次,出栈顺序为 2(top1) 21(top2);使用操作符运算:21 - 2

    结果为 19 入栈;

中缀 后缀表达式 四则运算
10. 扫描完毕,栈内元素即为最终结果 19;3 * (2 + 5) - 4 / 2 = 19;

代码实现

读取一串字符串,将其转换为后缀表达式,考虑如下几点:

  • 读取表达式会出现多位数例如 32;还会出现单个的数如 9;还会出现运算符 + - * / ( ),而不管是在转化还是在计算中,这些都是作为一个整体进行操作的,因此使用单一类型的栈或者队列无法完成怎样多样化的存储;例如 char 类型无法存储 32;使用 string 类型应该可行,但是在操作过程中必须对 该字符串到底是操作数还是操作符进行判断;因此此处采用结构体来作为栈或队列的元素类型;结构体如下:

    struct element {
        double num;
        char op;
        bool flag;
    };
    //当 flag 为 true 表示该结构体存储操作数,存储在 num 中,为 false 表示存储操作符,存储在 op 中
    
  • 另外在此过程中还涉及到运算符优先级的问题,此处采用 map 记录运算符的优先级:) ( 做另外的处理,不给出其优先级;

    不了解 map 的将其作为一种映射关系即可;即 + - 对应的优先级为 1,* / 为 2;

    map<char, int> op;
    op['+'] = op['-'] = 1;
    op['*'] = op['/'] = 2;
    
  • 当遇到连续字符为数字的处理方式,即若为一个多位数例如 32,如何才能将 32 存储起来;解决方式为:读取到第一个数字后进行循环判断一直到读取完毕或者遇到不是数字的操作符,若进入循环,那么此次读入的数为 前一个 * 10 + 现在的数字;

    /*... */ 前一个读取到一个数字
    while(当前位置 < 总长度 && 当前元素为数字) {
    	将要存入元素 = 前一个元素 * 10 + 当前元素;
    	当前位置++;
    }
    
  • 整体代码如下:

    #include<iostream>
    #include<stack>
    #include<string>
    #include<queue>
    #include<map>
    using namespace std;
    
    struct element {
    	double num;		//结构体为操作数时存放操作数 
    	char op;		// 结构体为操作数时存放操作数 
    	bool flag;		//true 表示为操作数,flase 表示为 操作符 
    };
    
    string str;			//输入的中缀表达式
    stack<element> s;	//操作符栈
    queue<element> q;	//后缀表达式队列
    map<char, int> op;	//操作符优先级判定
    
    void suffix() {		//将 中缀表达式 str 转化为 后缀表达式 q 
    	element temp;
    	int index = 0;	//当前判断中缀表达式位置
    	while(index < str.size()) {
    		if(str[index] >= '0' && str[index] <= '9') {	//当前读取元素为数字 
    			temp.flag = true;
    			temp.num = str[index++] - '0';		//将字符型转化为数字的方式
    			while(index < str.size() && str[index] >= '0' && str[index] <= '9') {	//处理读取到多位数如 32 
    				temp.num = temp.num * 10 + str[index] - '0';
    				index++;
    			}
    			cout << temp.num << "加入后缀表达式" << endl; 
    			q.push(temp);
    		} else {
    			temp.flag = false;			
    			temp.op = str[index];
    			if(str[index] == '(') {			//处理 ( ),为 ( 直接入栈 
    				s.push(temp);
    				index++;
    				cout << temp.op << "入栈" <<endl;
    				continue;
    			} else if(str[index] == ')') {	//为 ) 出栈到遇 ( 为止 
    				while(s.top().op != '(') {
    					q.push(s.top());		//出栈元素加入到后缀表达式 
    					cout << s.top().op << "出栈" << endl;
    					s.pop();
    				}
    				s.pop();					//将 ( 出栈 
    				index++;
    				continue;
    			}
    			temp.flag = false;			
    			temp.op = str[index];
    			while(!s.empty() && op[temp.op] <= op[s.top().op]) {	//当前操作符优先级低于栈顶且栈不为空时 
    				q.push(s.top());
    				cout << s.top().op << "出栈" << endl;
    				s.pop();
    			}
    			cout << temp.op << "入栈" << endl;
    			s.push(temp);
    			index++;
    		}
    	} 
    	while(!s.empty()) {
    		q.push(s.top());
    		cout << s.top().op << "出栈" << endl;
    		s.pop();
    	}
    } 
    
    
    double Cal() {
    	double top1, top2;
    	element cur, temp;
    	while(!q.empty()) {		//后缀表达式队列从头依次读取 
    		cur = q.front();	//cur 记录队首便于后续操作
    		q.pop();			//出队列,当前判断元素已经存放在 cur
    		if(cur.flag == true) {
    			s.push(cur);	//当前元素为操作数直接入栈 
    		} else {
    			top1 = s.top().num;
    			s.pop();
    			top2 = s.top().num;
    			s.pop();
    			temp.flag = true;	//temp 记录运算结果
    			if(cur.op == '+') {
    				temp.num = top2 + top1;
    			} else if(cur.op == '-') {
    				temp.num = top2 - top1;
    			} else if(cur.op == '*') {
    				temp.num = top2 * top1;
    			} else if(cur.op == '/') {
    				temp.num = top2 / top1;
    			} 
    			s.push(temp);
    		}
    	}
    	return s.top().num;
    }
    
    int main() {
    	op['+'] = op['-'] = 1;
    	op['*'] = op['/'] = 2;
    	str = "3 * (2 + 5) - 4 / 2";
    	while(!s.empty()) {				//清空栈 
    		s.pop();
    	} 
    	for(int i = 0; i < str.size(); i++) {		//去掉空格 
    		if(str[i] == ' ') {
    			str.erase(str.begin() + i);
    		}
    	} 
    	suffix();
    //	while(!q.empty()) {				//查看后缀表达式,此处查看将会清空队列 
    //		element e = q.front();
    //		if(e.flag) {
    //			cout << q.front().num << " ";
    //		} else {
    //			cout << q.front().op << " ";
    //		}
    //		q.pop();
    //	} 
    // 查看结果 : 3 2 5 + * 4 2 / -
    	cout << "最终结果:" << Cal() << endl;
    	return 0;
    }
    
    3加入后缀表达式
    *入栈
    (入栈
    2加入后缀表达式
    +入栈
    5加入后缀表达式
    +出栈
    *出栈
    -入栈
    4加入后缀表达式
    /入栈
    2加入后缀表达式
    /出栈
    -出栈
    最终结果:19
    

    可见,结果于分析一致;至于操作数属于多位的情况,也能够成立,读者可以自己修改例子尝试;

其他

至此,一个能够处理整数的简易计算器便实现了;

当然本例中采取的方式有点复杂,更简单的做法可以直接通过分隔符分离;例如 32 + 3.14 按空格直接分离可以直接得到操作数和操作符 32, +, 3.14 这样便很容易的分离出操作数,不管其为一个多位数还是浮点数,然后剩余的原理部分与本例相同;

一个用 Java 采用此类方式实现的简易计算器:链接

上一篇:poj 3264 Balanced Lineup (RMQ)


下一篇:【题解】BZOJ-4176 Lucas的数论