使用栈解决的一类经典问题:表达式转换及求值;中缀表达式;前缀表达式,后缀表达式,中缀转前缀;中缀转后缀;后缀表达式求值;波兰式,逆波兰式

本文详细介绍了中缀、前缀和后缀表达式之间的转换方法,包括手动转换和利用栈实现的转换算法。针对考研中的经典问题,阐述了如何通过括号处理和栈操作将表达式在三种形式间转换。同时,文章还讲解了如何使用栈来计算前缀和后缀表达式,以实现表达式的求值,例如LeetCode的逆波兰式求值问题。
摘要由CSDN通过智能技术生成

背景知识

中缀表达式a+b

前缀表达式(波兰式)+ab

后缀表达式(逆波兰式)ab+

表达式转换问题(考研经典)

一:手工转换

考研中有一类经典的问题就是表达式的转换问题,经常要把一个类型的表达式转换为另一个类型的表达式

在这里插入图片描述
所以这里我们主要说一下中缀转前缀前缀转中缀中缀转后缀后缀转中缀即可。这样这三种形式任意两个之间的转换都可以借助上面四种转换直接或间接完成

所用表达式的三种形式如下
在这里插入图片描述

(1)中缀转前缀和中缀转后缀

首先每遇到两个操作数,一个运算符就把他们用括号扣起来(用括号括起来的可以看做一个操作数)
如下
在这里插入图片描述
把上面的结果全部抄一遍(对应位置,不要错乱),抄的时候不要抄操作数,把操作数的位置空出来。然后依据原来的结果,把运算符移出到括号前面面,注意只需要移动到该运算符原来所在的括号前面就可以了,不要一次移动多层

如下
在这里插入图片描述
转化为后缀表达式时,只需要移动到括号后面就可以了,由于非常简单,这里我就不演示了,结果在最上面

(2)前缀转中缀和后缀转中缀

前缀转中缀时,从后向前扫描前缀表达式,每遇到两个操作数一个运算符就把运算符放到两个操作数中间,注意有的时候可能会连续遇到三个及以上的操作数,这时就不要管后面的操作数了,你只需要向前找,一直找到运算符然后结合后面的两个操作数组合在一起

如下,其中红色部分表示待处理的运算符和操作数
在这里插入图片描述

在这里插入图片描述
对于后缀转中缀,和前缀转中缀实则是一样的,只不过后缀转中缀的时候需要从前向后扫描,由于也比较简单这里就不演示了

二:用栈实现表达式转换

表达式转换是栈的应用的一个非常典型的例子,主要利用的就是栈的先进后出的特性,代码的逻辑不复杂,但是就是比较多一点

这里用栈实现表达式转换主要指的是中缀转前缀或者后缀,后其他形式的表达式的转换没有具体的意义,因为转为前缀和后缀的目的就是让计算机进行求值,所以这里主要讲的就是中缀转换为前缀或后缀的代码。由于C语言中处理栈比较麻烦,所以这里采用C++,省去一些造轮子的步骤

(1)中缀转后缀

中缀式转后缀式需要两个栈完成,然后从后向前扫描中缀表达式,先不讨论怎样入栈,记住:所有的操作数入S1栈,所有的括号和运算符入S2栈

如下,首先由于S2空,此时扫描到了左括号,遇到左括号直接入栈
在这里插入图片描述
接着此时扫描到了操作数a,直接入S1,所有操作数直接入S1
在这里插入图片描述

接着此时扫描到了“+”号,此时S2栈顶元素为左括号,任何运算符遇到左括号直接入栈
在这里插入图片描述
继续进行,扫描到b直接入S1。然后此时扫描到了右括号,当扫描到右括号时将S2中从栈顶元素到左括号之间的元素依次出S2,入S1,但是左括号忽略掉,也就是不要了

在这里插入图片描述
接着扫描到“*”,入S2,然后扫描到操作数“c”,入S1。此时扫描到“+”,由于扫描到的元素符优先级小于等于S2栈顶运算符优先级,故将栈顶运算符出栈入S1,然后接着再拿扫描到的运算符与新的栈顶元素比较,如果还是小于等于重复上述步骤,如果是大于则直接入栈,这里由于“*”出栈后,S2为空,故直接入“+”

在这里插入图片描述

接着再入操作数d。此时来到运算符-,由于扫描到的运算符的优先级小于等于栈顶元素优先级,故重复
在这里插入图片描述

剩余部分就不再演示了,此时大家观看S1栈,是不是已经有了后缀式的雏形了呢

代码如下

#include <iostream>
#include <stack>
#include <string>
using namespace std;

int getpriority(char c)
{
	if (c == '+' || c == '-')
	{
		return -1;//加减优先级小
	}
	else
	{
		return 1;//乘除优先级大
	}
}


void convert(string& express, stack<char>& s1, stack<char>& s2)
{
	int i = 0;
	while (express[i] != '\0')//扫描中缀表达式
	{
		if ('0' <= express[i] && express[i] >= '9')//如果扫描到了操作数,直接入s1
		{
			s1.push(express[i++]);
		}
		else if (express[i] == '(')//如果扫描到了左括号,直接入s2
		{
			s2.push(express[i++]);
		}
		else if (express[i] == '+' || express[i] == '-' || express[i] == '*' || express[i] == '/')//扫描到运算符进行优先级判断
		{
			if (s2.empty() || s2.top() == '(' || getpriority(express[i]) > getpriority(s2.top()))//如果此时S2为空或者栈顶元素为左括号,或者扫描到的运算符优先级大于栈顶运算符优先级,则S2
			{
				s2.push(express[i++]);
			}
			else//反之优先级如果是小于等于的话,那么就要把运算符出栈然后入S1
			{
				char temp = s2.top();
				s2.pop();
				s1.push(temp);
			}
		}
		else if (express[i] == ')')//最后一种情况就是扫描到了右括号,那么就把S2从栈顶到左括号的元素依次出栈入栈
		{
			while (s2.top() != '(')
			{
				char temp = s2.top();
				s2.pop();
				s1.push(temp);
			}
			//注意最后停止循环的时候S2的栈顶元素是左括号,但是不要把左括号入栈,所以直接丢掉左括号
			s2.pop();
			i++;//不要忘记后移
		}
	}
	while (!(s2.empty()))//如果S2没有空,那么依次出S2,入S1
	{
		char temp = s2.top();
		s2.pop();
		s1.push(temp);
	}


}

int main()
{
	stack<char> s1;//结果栈,入操作数
	stack<char> s2;//辅助栈,入运算符和括号

	stack<char> result;//输出用

	string expression("(a+b)*c+d-(e+g)*h");
	cout << "转换前为中缀式:" << expression << endl;
	convert(expression, s1, s2);
	cout << "转换为中缀式:";

	while (!(s1.empty()))
	{
		char temp = s1.top();
		s1.pop();
		result.push(temp);
	}
	while (!(result.empty()))
	{
		cout << result.top();
		result.pop();
	}




}

在这里插入图片描述

(2)中缀转前缀

中缀转前缀基本和中缀转后缀一致,不同点如下

  • 扫描时从后向前扫描中缀式
  • 遇到右括号直接入栈,遇到左括号进行出栈
  • 扫描到的运算符优先级如果大于等于栈顶运算符优先级,直接入栈
void convert2(string& express, stack<char>& s1, stack<char>& s2)//中缀转前缀
{
	int i = express.size()-1;
	while (i>=0)//扫描中缀表达式
	{
		if ('a' <= express[i] && express[i] <= 'z')//如果扫描到了操作数,直接入s1
		{
			s1.push(express[i--]);
		}
		else if (express[i] == ')')//如果扫描到了右括号,直接入s2
		{
			s2.push(express[i--]);
		}
		else if (express[i] == '+' || express[i] == '-' || express[i] == '*' || express[i] == '/')//扫描到运算符进行优先级判断
		{
			if (s2.empty() || s2.top() == ')' || getpriority(express[i]) >= getpriority(s2.top()))//如果此时S2为空或者栈顶元素为左括号,或者扫描到的运算符优先级大于等于栈顶运算符优先级,则入S2
			{
				s2.push(express[i--]);
			}
			else//反之优先级如果是大于等于的话,那么就要把运算符出栈然后入S1
			{
				char temp = s2.top();
				s2.pop();
				s1.push(temp);
			}
		}
		else if (express[i] == '(')//最后一种情况就是扫描到了左括号,那么就把S2从栈顶到右括号的元素依次出栈入栈
		{
			while (s2.top() != ')')
			{
				char temp = s2.top();
				s2.pop();
				s1.push(temp);
			}
			//注意最后停止循环的时候S2的栈顶元素是右括号,但是不要把右括号入栈,所以直接丢掉右括号
			s2.pop();
			i--;//不要忘记后移
		}
	}
	while (!(s2.empty()))//如果S2没有空,那么依次出S2,入S1
	{
		char temp = s2.top();
		s2.pop();
		s1.push(temp);
	}


}

在这里插入图片描述

表达式计算问题(使用栈实现)

我们转换表达式的目的就在于让计算机实现表达式的求值,或者说,计算机是不认识也不清楚中缀式的含义的,只有人能读懂,但是我们转换为前缀或者后缀后却可以借助栈的性质来完成计算

这里我们就以LeetCode 150:逆波兰式求值,也就是后缀表达式求值为例,前缀式基本一致
在这里插入图片描述

求解时,准备一个栈,从左向右扫描后缀表达式,遇到操作数就入栈,遇到运算符则出栈两个操作数,先出栈的作为右操作数,后出栈的作为左操作数,然后运算之后把结果继续压入栈内,重复以上步骤,最后栈中的栈顶元素就是结果
在这里插入图片描述

class Solution {
public:
    void calculate(int& opnd1,string& op,int& opnd2,int& result)//计算
    {
        if(op=="+")
            result=opnd1+opnd2;
        if(op=="-")
            result=opnd1-opnd2;
    
        if(op=="*")
            result=opnd1*opnd2;
        if(op=="/")
            result=opnd1/opnd2;
    }

    int evalRPN(vector<string>& tokens) 
    {
        stack<int> ret;//结果栈
        for(int i=0;i<tokens.size();i++)
        {
            if(tokens[i].size()==1 && (tokens[i] =="+" || tokens[i] =="-" || tokens[i] =="*" || tokens[i] =="/" ))//运算符
            {
                int opnd1,opnd2,result;//两个操作数和结果
                string op;//运算符

                op=tokens[i];//运算符
                opnd2=ret.top();
                ret.pop();//先出来的是右操作数
                opnd1=ret.top();
                ret.pop();//后出来的是左操作数


                calculate(opnd1,op,opnd2,result);

                ret.push(result);//计算结果仍然压入栈

            }
            else//操作数
            {
                int temp=atoi(tokens[i].c_str());
                ret.push(temp);
            }

        }
        return ret.top();//栈顶元素是结果
    }
};

在这里插入图片描述
前缀表达式和这个基本一致,不同点就是要从后向前扫描,然后先出栈的是左操作数,后出栈的是右操作数

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐江湖

创作不易,感谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值