中缀表达式转后缀表达式(逆波兰表达式)及如何计算后缀表达式

目录

中缀、后缀表达式简介

中缀转后缀的规则

模拟中缀转后缀 

中缀转后缀代码 

后缀表达式求值

后缀表达式求值代码

Leetcode相关题目 


中缀、后缀表达式简介

首先说说什么是中缀表达式,中缀表达式中,操作符是以中缀形式处于操作数的中间。例如,在表达式“3 + 4”中,“+”是中缀操作符,位于两个操作数“3”和“4”之间。对于我们而言,它最直观。下面说说后缀表达式,

逆波兰表达式(Reverse Polish Notation,RPN),也被称为后缀表达式,是一种算术表达式的表示方法。在这种表示法中,运算符被写在操作数的后面,而不是像常规的中缀表达式那样写在操作数的中间。逆波兰表达式不使用括号来指示运算的优先级,而是完全依赖于运算符出现的顺序以及运算符本身的优先级规则。也就是说,后缀表达式严格遵循从左往右计算。

中缀转后缀的规则

下面是中缀转后缀的思维导图,有一些细节不方便体现,我们到代码实现部分在谈。

模拟中缀转后缀 

根据上面思维导图中的规则,我们来模拟一下中缀转后缀。

希望通过上面的一个简单例子,我们明白了中缀转后缀的规则。上面被框起来的部分,是个细节,我在写代码的时候犯了这样的错误——我在把+出栈后,直接让-入栈,可把我坑惨了。出现错误后,我再次看了一遍代码,重点看了这一部分,但是觉得很对,一点都没错,哎。所以,我错在哪了?为什么不可以直接让-进栈?

我们在思考这个问题的时候不要局限于上面的图,因为照着上面的话感觉我犯的错误很对。

我错就错在我以为栈内就只有+这个运算符,+运算符出栈后,栈为空,所以直接把 - 运算符入栈。但实际上,栈内可能还有其他运算符,而且优先级低于或等于栈顶运算符。此时,应该让栈顶运算符出栈,而不是把 - 直接丢入栈内。也就是说,当前运算符 - 还要继续走前面运算符的逻辑。如果这句话暂时不能理解,没关系,看了代码就明白了。

下面我们上个看起来比较复杂的式子练练手。

其中,绿色的栈是递归时创建的。上面的例子值得好好理解。

中缀转后缀代码 

好了,我就不再废话了,上代码!

#include <iostream>
#include <vector>
#include <stack>
#include <map>
using namespace std;

//下面需要比较优先级的高低,由于无法直接使用ASCII比较,故进行一下定义
map<char, int> pro = { {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2} };

void toRPN(const string& s, size_t& i, vector<string>& v)
{
	int n = s.size();
	stack<char> st;//存储操作符
	while (i < n)
	{
		//isdigit为C语言的一个函数,功能是判断字符串中的某个字符是否是数字
		if (isdigit(s[i]))
		{
			//细节:数字不一定是1位数,所以需要提取出完整的数字
			string tmp;
			while (i < n && isdigit(s[i])) tmp += s[i++];
			v.push_back(tmp);
		}
		else if (s[i] == '(')
		{
			//递归处理
			i++;//跳过'('
			toRPN(s, i, v);
		}
		else if (s[i] == ')')
		{
			//结束递归
			while (!st.empty())
			{
				v.push_back(string(1, st.top()));//查文档——string的构造函数
				st.pop();
			}
			i++;//跳过')'
			return;
		}
		else
		{
			//走到这说明就是操作符了
			if (st.empty() || pro[st.top()] < pro[s[i]])
				st.push(s[i++]);
			else
			{
				v.push_back(string(1, st.top()));
				st.pop();//这里不能让i++,之前解释过了
			}
		}
	}

	//走到这,说明计算式或子表达式结束了,输出栈中所有运算符
	while (!st.empty())
	{
		v.push_back(string(1, st.top()));
		st.pop();
	}
}

int main()
{
	vector<string> v;
	size_t i = 0;
	string s = "1+2-(3*4+5)-7";
	toRPN(s, i, v);
	for (auto s : v) cout << s << " ";
	cout << endl;
	return 0;
}

 到目前,你可能会疑惑会质疑,空格不需要处理吗?或者说有空格的话上面程序会不会崩掉?

空格是需要处理的,上面的函数是在处理完原字符串中的空格后再调用的,因为我不想在该函数内部处理空格,如果你想的话也可以,多加个判断就好了。

除了这个问题,代码中还有一个细节——仔细看看toRPN函数的参数。

我是不是都用了引用?因为我们希望程序在递归时处理的也是相同的字符串,递归时处理的结果也可以影响到上层,这是我们想要的。换句话说,s,i,v,为整个程序共用。

后缀表达式求值

前面在介绍后缀表达式的时候提到,后缀表达式严格按照从左到右计算。在计算后缀表达式时,我们需要借助一个栈(当然你可以用其他数据结构模拟栈)来存储数字。

 1 2 + 3 4 * 5 + - 7 -,以此表达式为例。

从左到右遍历表达式。

1)遇到运算数,直接入栈。

2)遇到操作符,取出栈中的两个操作数运算,运算结果存入中栈中。(细节下面说)

3)重复以上步骤,当遍历完表达式,栈顶元素即为答案。

有些操作符,比如 ‘-’ 和 ‘/’,它们是区分左右操作数的,即a - b 和 b - a 的结果可能不一样。这该怎么办?

栈的性质——后进先出。我们从左往右遍历,一定是左操作数先进栈,右操作数后进栈,所有先出的是右操作数,后出的是左操作数,这样就可以区分左右操作数了。其实,这就是选择栈来存储数据的原因。

后缀表达式求值代码

int calculate(vector<string>& s)
{
	stack<int> st;
	for (auto str : s)
	{
		if (str == "+" || str == "-" || str == "*" || str == "/")
		{
			int right = st.top();
			st.pop();
			int left = st.top();
			st.pop();
			int ans = 0;
			switch (str[0])
			{
			case '+': ans = left + right; break;
			case '-': ans = left - right; break;
			case '*': ans = left * right; break;
			case '/': ans = left / right; break;
			}
			st.push(ans);
		}
		else
		{
			st.push(stoi(str));
		}
	}
	return st.top();
}
int main()
{
	vector<string> v;
	size_t i = 0;
	string s = "1+2-(3*4+5)-7";
	toRPN(s, i, v);
	cout << calculate(v) << endl;

	return 0;
}

Leetcode相关题目 

. - 力扣(LeetCode)

上面这题可以我们本片博客讲到的方法解决,虽然不是最简洁的解法,但是可以练练我们上面讲到的方法。代码我在下面贴一份。

class Solution {
public:
    int calculate(string s) {
        vector<string> v;
        stack<int> st;
        size_t i = 0;
        toRPN(s, i, v);
        for(auto& str : v)
        {
            if(str == "+" || str == "-" || str == "*" || str == "/")
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                int ans = 0;

                switch(str[0])
                {
                    case '+': ans = left + right; break;
                    case '-': ans = left - right; break;
                    case '*': ans = left * right; break;
                    case '/': ans = left / right; break;
                }
                st.push(ans);
            }
            else
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }

    //中缀转后缀
    map<char, int> pro = { {'+', 1}, {'-', 1}, {'*', 2}, {'/',2} };
    void toRPN(const string& s, size_t i, vector<string>& v)
    {
        int n = s.size();
        stack<char> st;
        while(i < n)
        {
            if(s[i] == ' ') i++;
            else if(isdigit(s[i]))
            {
                //提取数字
                string tmp;
                while(i < n && isdigit(s[i])) tmp += s[i++];
                v.push_back(tmp);
            }
            else
            {
                if(st.empty() || pro[s[i]] > pro[st.top()])
                    st.push(s[i++]);
                else
                {
                    v.push_back(string(1, st.top()));
                    st.pop();
                }
            }
        }

        while(!st.empty())
        {
            v.push_back(string(1, st.top()));
            st.pop();
        }
    }
};

本篇文章到这就结束啦~如有错误,请您不吝指出,谢谢!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值