中缀表达式转后缀表达式详解

一、思想介绍

(1)背景

​  中缀表达式是最常用的算术表达式形式——运算符在运算数中间。但运算时需要考虑运算符优先级

​  后缀表达式是计算机容易运算的表达式,运算符在运算数后面,从左到右进行运算,无需考虑优先级,运算呈线性结构

1 + 2 * 3// 中缀表达式
1 2 3 * +// 后缀表达式

​  计算机直接处理中缀表达式是比较困难的,以表达式1 + 2 * 3为例:当计算机读取到1 + 2后就可以直接得出结果3了吗?答案是否定的,因为后面可能会有优先级更高的运算符。

 ​ 就拿2来说,究竟是先和左操作数计算还是先与右操作数计算?这才是解决这类问题的核心与本质。

(2)中缀表达式转后缀表达式
1.最简单的情况:

​  后缀表达式也叫逆波兰表达式,我们可以通过以下的方法将中缀表达式转化为后缀表达式(我们用一个栈来临时存储中间遇到的操作符):

  1. 遇到操作数,直接存储/输出

  2. 遇到操作符

    • 栈为空 or 该操作符的优先级比栈顶的操作符的优先级高 → 将该操作符压栈
    • 该操作符的优先级比栈顶的操作符优先级低 or 相同 → 弹出栈顶操作符存储,并将该操作符压栈
  3. 遍历结束后将栈里的操作符依次全部弹出

    中缀表达式转后缀表达式详解_第1张图片

2.分析:

​  我们以 n1 n2 n3 n4…… 为例加以说明,其中 ni 表示操作数,Oi 表示操作符。

(1)如果 O1 的优先级高于 O2,那么毫无疑问,n2 会先与左操作数运算。

(2)如果 O1 的优先级低于 O2,那么可以让 n2 与右操作数 n3 发生运算吗?答案是否定的!因为n3 能不能先与 n2 运算还取决于 O2 O3 运算符优先级的高低。但是目前我们可以确定 O2 运算符肯定在 O1 运算符前被使用。

​  能够存储这种先进后出模式的数据结构,栈自然是不二之选!!

3.问题延伸:
  1. 对于括号怎么处理?

    ​  从上面的学习中我们不难发现,先出栈的运算符一定比后出栈的运算符先运算。而括号作为优先级最高的运算符,我们如何体现出它的“优越性”呢?

    ​  其实处理的办法很简单,进入左括号后遇到的第一个运算符无脑压栈,不管优先级高低。这样就可以保证优先运算。

    ​  其实可以将括号里的式子看成一个个独立的式子,式子的结束就是遇到右括号的时候。在结束时,我们需要把栈里的操作符依次全部弹出(左括号之上的操作符)

  2. 对于负数怎么处理?

    负数会在什么情况下出现呢?

    • 表达式的开始
    • 括号的开始

    我们只需在遇到负数时首先存储一个0,这样就可以通过减法来模拟负数

(3)后缀表达式的最终求解

​  转化为后缀表达式后,我们就不需要考虑运算符优先级的高低,利用下面的方法就可以求出最终的结果:

  • 遇到操作数 → 直接入栈

  • 遇到操作符 → 取出栈顶的两个数据进行运算,将计算结果压栈

    中缀表达式转后缀表达式详解_第2张图片

(注意:转换过程中操作数的相对位置始终没有发生改变,所以栈最顶上的是右操作数,它下面的才是左操作数)

(4)参考代码

【作用】:将中缀表达式转后缀,并计算出最终的结果

#include 
#include 
#include 
#include 
#define priority(c)  (c == '*' || c == '/')? (1) : (0)
//[解释]:宏定义一个优先级比较函数
//[注意]:为了避免宏定义一些意向不到的错误,建议多加括号
using namespace std;

class calc
{
public:
	 // 例:s = “1 + 6 / 3”
    int eval(const string& s)
	{
		int sz = s.size();
		if (s[0] == '-') //[解释]:对负数的处理,通过减法来模拟负数
			num.push(0);

		for (int i = 0; i < sz; i++)
		{
			if (s[i] == ' ')
				continue;
			else if (isdigit(s[i])) //[作用]:字符串转数字的操作
			{
				int n = s[i] - '0';
				while (i + 1 < sz && isdigit(s[i + 1]))
					n = n * 10 + (s[++i] - '0'); 
                //[注意]:括号不能少,否则有可能溢出
				num.push(n);
			}
			else if (s[i] == '(')
			{
				op.push('(');
				flag = true;//[解释]:标记进入左括号,用于无脑压栈 
				if (s[i + 1] == '-')
					num.push(0);
			}
			else if (s[i] == ')')
			{
				flag = false;
                // [注意]:在遇到括号里第一个运算符后,flag才会置为
                // false,但遇到括号里无运算符的情况就可能会出错,所
                // 以加个双保险
				char _operator = op.top();
				while (_operator != '(')
				{
                   	// [解释]:弹出运算符进行运算的过程
					count_push(_operator);
					op.pop();
					_operator = op.top();
				}
				op.pop();  // 把'('弹出来
			}
			else
			{
				if (op.empty() || flag)
				{
					op.push(s[i]);
					flag = false;
				}
				else 
				{
					char _operator = op.top();
					if (priority(_operator) >= priority(s[i]))
					{
						count_push(_operator);
						op.pop();
					}
					op.push(s[i]);
				}
			}
		}
		while (!op.empty()) //[解释]:遍历结束后将栈里的操作符依次全部弹出
		{
			count_push(op.top());
			op.pop();
		}
		return num.top();
	}
    
    //[作用]:进行栈顶两个数据的运算并压入栈中 
	void count_push(char _operator)
	{
		int tmp = 0;
		int right = num.top();
		num.pop();
		int left = num.top();
		num.pop();
		switch (_operator)
		{
		case '+':
			tmp = left + right;
			break;
		case '-':
			tmp = left - right;
			break;
		case '*':
			tmp = left * right;
			break;
		case '/':
			tmp = left / right;
			break;
		}
		num.push(tmp);
	}

	
private:
	bool flag = false;   //[作用]:用来标记是否进入左括号
	stack<int> num;	     //[作用]:存储数据的栈
	stack<char> op;      //[作用]:存储运算符的栈
};

(5)小试牛刀

150. 逆波兰表达式求值

224. 基本计算器

你可能感兴趣的:(【算法❃思维与技巧】,c++,算法)