中缀表达式转后缀表达式学习记录
一点不知道是什么东西的东西
背景
想做洛谷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