中缀表达式转后缀表达式学习记录

一点不知道是什么东西的东西

背景

想做洛谷P1054,想了想可以在同模数的情况下带质数算值比较,但是我又不会怎么带值,上"b站"上一搜可以用中缀表达式转后缀表的方法,学习后有了这篇笔记。

一个栗子

首先,我们举一个中缀表达式的栗子(这个栗子应该非常全了,基本包括所有的符号(除了"^"、"%")及优先级调整,如果不全请提出,我会尽力补全):$9+(\ 9+8\ /\ 4-9+6\ )8-7+(\ (\ 2+3\ )3\ )$ (对就是万恶的小学四则运算)

而它的后缀表达式 就是:$9\ 9\ 8\ 4\ / + 9 - 6 + 8 * + 7 - 2\ 3 + 3 * +$

后缀表达式的概念

Wikipedia上的概念

​ 逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

某度上的概念

​ 后缀表达式,又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。

不明白的话,就不明白吧,反正我学了之后也不明白,只知道咋用。。。

正片:中缀表达式转后缀表达式

概念&&方法

1、任何中缀表达式都由运算数,运算符,括号,这三部分组成。

2、从中缀表达式的左边开始扫描,若遇到运算数时,则直接将其输出(不压入堆栈)。

3、若遇到左括号,则将其压栈。

4、若遇到右括号,表达括号内的中缀表达式已经扫描完毕。这时需将栈顶的运算符依次弹出并输出,直至遇到左括号[左括号弹出但不输出]。

5、若遇到的是运算符:a、如果该运算符的优先级大于栈顶运算符的优先级时,将其压栈

b、如果该运算符的优先级小于栈顶运算符的优先级时,将栈顶运算符弹出并输出,接着和新的栈顶运算符比较,若大于,则将其压栈,若小于,继续将栈顶运算符弹出并输出......(一直递归下去,直至运算符大于栈顶运算符为止)。

6、最后一步,若扫描到中缀表达式的末尾[即扫描结束],若堆栈中还有存留的运算符依次弹出并输出即可。

手动模拟

​ 看了上面的方法很多人应该是懵的,我也是,所以我尝试找一个模拟。

​ 但是......

​ 我查了好久没有查到特别全的模拟,所以,自己动手丰衣足食,我就手打了一个比较全的,也就是前面的栗子(当然没有加入"^"、"%",暂时这样吧因为我到现在没搞明白这两个东西的优先级...)

扫描到的元素 $S2$(栈底->栈顶) $S1$(栈底->栈顶) 说明
$9$ $9$ 数字,直接入栈
$+$ $9$ $+$ $S1$为空,直接入栈
$($ $9$ $+\ ($ 左括号,直接入栈
$9$ $9\ 9$ $+\ ($ 数字,直接入栈
$+$ $9\ 9$ $+\ (\ +$ $S1$栈顶为左括号,直接入栈
$8$ $9\ 9\ 8$ $+\ (\ +$ 数字,直接入栈
$/$ $9\ 9\ 8$ $+\ (\ +\ /$ $S1$栈顶为"$+$",优先级小于"$/$",直接入栈
$4$ $9\ 9\ 8\ 4$ $+\ (\ +\ /$ 数字,直接入栈
$-$ $9\ 9\ 8\ 4\ /\ +$ $+\ (\ -$ $S1$栈顶为"$/$",优先级大于"$-$",将$S2$弹出至左括号(比"$/$"优先级更大的第一个运算符)并压入"$-$"
$9$ $9\ 9\ 8\ 4\ /+9$ $+\ (\ -$ 数字,直接入栈
$+$ $9\ 9\ 8\ 4\ /+9\ -$ $+\ (\ +$ $S1$栈顶为"$-$","$+$"优先级等于"$-$",将$S2$弹出至左括号(比"$-$"优先级更大的第一个运算符)并压入"$+$"
$6$ $9\ 9\ 8\ 4\ /+9-6$ $+\ (\ +$ 数字,直接入栈
$)$ $9\ 9\ 8\ 4\ /+9-6\ +$ $+$ 右括号,将$S2$弹出至左括号
$*$ $9\ 9\ 8\ 4\ /+9-6\ +$ $+\ *$ $S1$栈顶为"$+$",优先级小于"$*$",直接入栈
$8$ $9\ 9\ 8\ 4\ /+9-6\ +8$ $+\ *$ 数字,直接入栈
$-$ $9\ 9\ 8\ 4\ /+9-6\ +8*+$ $-$ $S1$栈顶为"$$",优先级大于"$-$",弹出至比"$$"优先级更大的第一个运算符(即栈底)并压入"$-$"
$7$ $9\ 9\ 8\ 4\ /+9-6\ +8*+7$ $-$ 数字,直接入栈
$+$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7\ -$ $+$ $S1$栈顶为"$-$","$+$"优先级等于"$-$",弹出至比"$-$"优先级更大的第一个运算符(即栈底)并压入"$+$"
$($ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7\ -$ $+\ ($ 左括号,直接入栈
$($ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7\ -$ $+\ (\ ($ 左括号,直接入栈
$2$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7-2$ $+\ (\ ($ 数字,直接入栈
$+$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7-2$ $+\ (\ (\ +$ $S1$栈顶为左括号,直接入栈
$3$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7-2\ 3$ $+\ (\ (\ +$ 数字,直接入栈
$)$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7-2\ 3\ +$ $+\ ($ 右括号,将$S2$弹出至左括号
$*$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7-2\ 3\ +$ $+\ (\ *$ $S1$栈顶为左括号,直接入栈
$3$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7-2\ 3+3$ $+\ (\ *$ 数字,直接入栈
$)$ $9\ 9\ 8\ 4\ /+9-6\ +8*+\ 7-2\ 3+3\ *$ $+$ 右括号,将$S2$弹出至左括号
到达最右端 $9\ 9\ 8\ 4\ /+9-6\ +8+\ 7-2\ 3+3+$ 弹出所有$S1$中剩下的运算符

所以我们可以通过模拟上表中的S1、S2来模拟中缀->后缀的过程。

代码

#include<bits/stdc++.h>
using namespace std;
 
map<char,int> p;
 
struct Node
{
	double num;//操作数
	char op;//操作符
	bool flag;//true表示操作数,false表示操作符
};
typedef struct Node node;
 
stack<node> s1;//操作符栈(意义同表)
queue<node> s2;//后缀表达式队列(意义同表)

void change(string str)
{
	node temp;
	for (int i = 0; i < str.length();)
	{
		 if (str[i] == '(')//3.遇到左括号:将其压栈
		 {
			temp.flag = false;
			temp.op = str[i];
			s1.push(temp);
			i++;
		}
		else if (str[i] == ')')//4.遇到右括号:执行出栈操作,输出到后缀表达式,直到弹出的是左括号
		{
			while (!s1.empty() && s1.top().op != '(')
            {
				s2.push(s1.top());
				s1.pop();
			}
			s1.pop();//弹出左括号**一定要注意将左括号弹出去,否则后面如果有右括号就jj了**
			i++;
		}
		else if (isdigit(str[i]))
		{
			//如果是数字
			temp.flag = true;
			temp.num = str[i] - '0';
			i++;//后移一位,因为数字不一定是个位数
			while (i < str.length() && isdigit(str[i]))
			{
				temp.num = temp.num * 10 + (str[i] - '0');
				i++;
			}
			s2.push(temp);//操作数进入后缀表达式
		}
		else
		{
			//如果是操作符(如果处理空格可加入另一个else if,在此懒得写owo)
			//5.遇到其他运算符:弹出所有优先加大于或等于该运算符的栈顶元素,然后将该运算符入栈
			temp.flag = false;
			while (!s1.empty() && p[s1.top().op]>=p[str[i]])
			{
				s2.push(s1.top());
				s1.pop();
			}
			temp.op = str[i];
			s1.push(temp);
			i++;
		}
	}
	//6.将栈中剩余内容依次弹出后缀表达式
	while (!s1.empty())
	{
		s2.push(s1.top());
		s1.pop();
	}
}
int main()
{	
	node now;
	string str;
	p['+'] = p['-'] = 1;//给予优先级
	p['*'] = p['/'] = 2;//给予优先级
	cin >> str;
	change(str);
	while (!s2.empty()){
		now = s2.front();
		if (now.flag == true) cout << now.num<<" "; 
		else cout << now.op<<" ";	
		s2.pop();
	}
 
	return 0;
}

相信如果之前的解释都认真看完了,那么这份代码应该是理解得了了。

后记:计算数值

为什么有这玩意

​ 既然已经有这个东西,那么不计算要它有个什么用。所以顺便把计算也学了

那么计算的过程

​ 1.从左到右扫描后缀表达式

​ 2.如果是数,压栈

​ 3.如果是符号,将弹出两个操作数,进行计算,计算的结果再压入栈中

最后栈顶的元素就是所要的值

也许。。。(只是也许!敲黑板!)你不明白这三个是什么东西,所以还是模拟一下

手动模拟

扫描到的元素 s3 说明
$9$ $9$ 数字,压栈
$9$ $9\ 9$ 数字,压栈
$8$ $9\ 9\ 8$ 数字,压栈
$4$ $9\ 9\ 8\ 4$ 数字,压栈
$/$ $9\ 9\ 2$ 符号,将"$4$"、"$8$"弹出,计算"$8/4$",得到"$2$",压栈
$+$ $9\ 11$ 符号,将"$2$"、"$9$"弹出,计算"$9+2$",得到"$11$",压栈
$9$ $9\ 11\ 9$ 数字,压栈
$-$ $9\ 2$ 符号,将"$9$"、"$11$"弹出,计算"$11-9$",得到"$2$",压栈
$6$ $9\ 2\ 6$ 数字,压栈
$+$ $9\ 8$ 符号,将"$6$"、"$2$"弹出,计算"$2+6$",得到"$8$",压栈
$8$ $9\ 8\ 8$ 数字,压栈
$*$ $9\ 64$ 符号,将"$8$"、"$8$"弹出,计算"$8*8$",得到"$64$",压栈
$+$ $73$ 符号,将"$64$"、"$9$"弹出,计算"$9+64$",得到"$73$",压栈
$7$ $73\ 7$ 数字,压栈
$-$ $66$ 符号,将"$7$"、"$73$"弹出,计算"$73-7$",得到"$66$",压栈
$2$ $66\ 2$ 数字,压栈
$3$ $66\ 2\ 3$ 数字,压栈
$+$ $66\ 5$ 符号,将"$3$"、"$2$"弹出,计算"$2+3$",得到"$5$",压栈
$3$ $66\ 5\ 3$ 数字,压栈
$*$ $66\ 15$ 符号,将"$3$"、"$5$"弹出,计算"$5*3$",得到"$15$",压栈
$+$ $81$ 符号,将"$15$"、"$66$"弹出,计算"$66+15$",得到"$81$",压栈
到达最右端 将$81$弹出,作为结果输出

贴份代码


#include<iostream>
#include<stack>
#include<queue>
#include<map>
#include<string>
#include<cstdio>
using namespace std;

map<char, int> p;

struct Node
{
	double num;//操作数
	char op;//操作符
	bool flag;//true表示操作数,false表示操作符
};
typedef struct Node node;
 
stack<node> s1;//操作符栈
queue<node> s2;//后缀表达式队列
stack<node> s3;//存放操作数的,为了计算后缀表达式的值

void change(string str)
{
	node temp;
	for (int i = 0; i < str.length();)
	{
		if (str[i] == '(')//3.遇到左括号:将其压栈
		{
			temp.flag = false;
			temp.op = str[i];
			s1.push(temp);
			i++;
		}
		else if (str[i] == ')')//4.遇到右括号:执行出栈操作,输出到后缀表达式,直到弹出的是左括号
		{
			while (!s1.empty() && s1.top().op != '(')
            {
				s2.push(s1.top());
				s1.pop();
			}
			s1.pop();//弹出左括号**一定要注意将左括号弹出去,否则后面如果有右括号就jj了**
			i++;
		}
		else if (isdigit(str[i]))
		{
			//如果是数字
			temp.flag = true;
			temp.num = str[i] - '0';
			i++;//后移一位,因为数字不一定是个位数
			while (i < str.length() && isdigit(str[i]))
			{
				temp.num = temp.num * 10 + (str[i] - '0');
				i++;
			}
			s2.push(temp);//操作数进入后缀表达式
		}
		else
		{
			//如果是操作符(如果处理空格可加入另一个else if,在此懒得写owo)
			//5.遇到其他运算符:弹出所有优先加大于或等于该运算符的栈顶元素,然后将该运算符入栈
			temp.flag = false;
			while (!s1.empty() && p[s1.top().op]>=p[str[i]])
			{
				s2.push(s1.top());
				s1.pop();
			}
			temp.op = str[i];
			s1.push(temp);
			i++;
		}
	}
	//6.将栈中剩余内容依次弹出后缀表达式
	while (!s1.empty())
	{
		s2.push(s1.top());
		s1.pop();
	}
}

double  calculate()
{
	double tmpa, tmpb;//临时的a、b(在计算的时候用到)
	node tmp, temp;
	while (!s2.empty())
	{//后缀队列非空
		tmp = s2.front();//记录队首元素
		s2.pop();
		if (tmp.flag == true)
		{//是数就压栈
			s3.push(tmp);
		}
		else
		{//是符号就运算
			tmpb = s3.top().num;
			s3.pop();//弹出第二个数(众所周知,先入后出)
			tmpa = s3.top().num;
			s3.pop();//弹出第一个数
			temp.flag = true;
			if (tmp.op == '+')
				temp.num = tmpa + tmpb;
			else if (tmp.op == '-')
				temp.num = tmpa - tmpb;
			else if (tmp.op == '*')
				temp.num = tmpa * tmpb;
			else
				temp.num = tmpa / tmpb;
			s3.push(temp);//把计算后的结果再次压栈
		}
	}
	return s3.top().num;
 
}

int main()
{
	string str;
	node now;
	p['+'] = p['-'] = 1;
	p['*'] = p['/'] = 2;
	cin >> str;
	change(str);
	// while (!s2.empty())
	// {
	// 	now = s2.front();
	// 	if (now.flag == true) cout << now.num << " ";
	// 	else cout << now.op << " ";
	// 	s2.pop();
	// }//之前输出后缀的函数
	while (!s3.empty())
	{
		s3.pop();
	}
	double answer=calculate();
	cout << answer << endl;
	return 0;
}

好了,希望能明白,第一次写学习笔记,有不足之处请多指教啦owo

posted @ 2019-08-05 13:51  fxk4_p  阅读(267)  评论(0编辑  收藏  举报