突然想在自己的Java作业的计算器中添加逆波兰算法,解决计算优先级的问题,写了写感觉问题蛮多的,算法学了就忘(lll¬ω¬)
在这里回忆下自己当时学习该算法的想法,尝试解释一下逆波兰算法的两个核心,中缀式转后缀式和后缀式的计算
1.中缀表达式转换为后缀表达式
中缀表达式就是我们平时的算数表达式的写法,运算符位于参与该运算数字的中间,例如6+2*3,直接处理该类的表达式存在困难,而处理后缀表达式则相对容易很多。转换时,利用栈存储运算符,遍历中缀式,遇到。。。
- 数字:直接输出至后缀式中
- 高优先级运算符或左括号( :即*、/、( ,直接入栈
- 低优先级运算符:即+和- ,弹栈直到遇到左括号或栈空,弹出的运算符输出到后缀式中,左括号不要弹出,最后此低优先级运算符入栈
- 右括号) :弹栈直到遇到左括号,弹出的运算符输出到后缀式中,左括号也要弹出但别手滑输出到后缀式中
- 遍历结束后,将栈中剩余的运算符依次弹出,输出到后缀式中
例如有一表达式6+2*(1+3)+8*9,开始遍历
- 6:输出到后缀式,此时后缀式为6
- 低优先级+运算符:此时栈空,入栈,此时栈内为+
- 2:输出到后缀式,此时后缀式为6 2
- 高优先级*运算符:直接入栈,此时栈内为+ *
- 左括号( :直接入栈,此时栈内为+ * (
- 1:输出到后缀式,此时后缀式为6 2 1
- 低优先级+运算符:开始弹栈,直接就遇到左括号( ,弹栈结束,+运算符入栈,此时栈内为+ * ( +
- 3:输出到后缀式,此时后缀式为6 2 1 3
- 右括号) :此时栈内为+ * ( +,开始弹栈,弹出+至后缀式后遇到左括号,最后栈内为+ * ,此时后缀式为6 2 1 3 +
- 低优先级+运算符:此时栈内为+ *,开始弹栈,依次弹出 *、+至后缀式,此时后缀式为6 2 1 3 + * +,+运算符入栈,此时栈内为+
- 8:输出到后缀式,此时后缀式为6 2 1 3 + * + 8
- 高优先级*运算符:直接入栈,此时栈内为+ *
- 9:输出到后缀式,此时后缀式为6 2 1 3 + * + 8 9
- 遍历已结束,栈内为+ *,依次弹出至后缀式,最终得到后缀式为6 2 1 3 + * + 8 9 * +
string get_suffix(string str) {//中缀转为后缀
stack<string> st_oper;//存储运算符的栈
int len = str.size();
string result;
for(int i = 0; i < len; i++) {
if(str[i] >= '0' && str[i] <= '9') {
int ends = i + 1;
while((str[ends] >= '0' && str[ends] <= '9') || str[ends] == '.')//支持小数
ends++;
result += str.substr(i, ends - i) + " ";//数字直接输出至后式
i = ends - 1;//注意每次循环结束的i++
} else {
string oper;
oper += str[i];//运算符转为字符串,便于判断
if(oper == "+" || oper == "-") {//低优先级运算符向前弹栈直到栈空或遇左括号
while(!st_oper.empty() && st_oper.top() != "(") {
result += st_oper.top() + " ";
st_oper.pop();
}
st_oper.push(oper);//别忘了低优先级运算符最后入栈
} else if(oper == "*" || oper == "/" || oper == "(") {//高优先级的运算符直接入栈
st_oper.push(oper);
} else if(oper == ")") {//开始匹配左括号
while(st_oper.top() != "(") {
result += st_oper.top() + " ";//括号之间栈内的运算符输出
st_oper.pop();
}
st_oper.pop();//记得最后弹出左括号,但不要输出至后缀式
}
}
}
while(!st_oper.empty()) {//弹出栈内最后剩余运算符
result += st_oper.top();
if(st_oper.size() != 1) {
result += " ";
}
st_oper.pop();
}
return result;
}
2.后缀表达式的计算
后缀式的计算依旧为遍历该后缀式,后缀式的各部分间以空格分开,此时采用栈存储数字而不是运算符,遇到。。。
- 数字:入栈
- 运算符:栈顶的数字num2和栈顶下一个的数字num1进行运算,注意运算顺序,若后缀式为num1 num2 /,那么运算为num1/num2而不是num2/num1。弹出这两个数字,运算结果入栈
例如上例的6 2 1 3 + * + 8 9 * +
- 遍历6、2、1、3:均直接入栈,此时栈内为6 2 1 3
- +运算符:此时栈内为6 2 1 3,弹出栈顶和栈顶下的两个数,即3和1,将1+3结果入栈,此时栈内为6 2 4
- *运算符:此时栈内为6 2 4,同上,将2*4结果入栈,此时栈内为6 8
- +运算符:此时栈内为6 8,同上,将6+8结果入栈,此时栈内为14
- 遍历8、9:均直接入栈,此时栈内为14 8 9
- *运算符:此时栈内为14 8 9,同上,将8*9结果入栈,此时栈内为14 72
- +运算符:此时栈内为14 72,同上,将14+72结果入栈,此时栈内为86
- 遍历结束,栈内只剩下一个元素86,即为最终结果
一定要注意运算顺序,例如1+5/2,后缀式为1 5 2 / +,第一步是5/2而不是2/5
double get_result(string str) {//计算后缀表达式获得结果
stack<double> st_num;//存储数字的栈
int len = str.size();
double num1, num2;
for(int i = 0; i < len; i++) {
int ends = i + 1;
while(ends < len && str[ends] != ' ')//我的后缀式中以空格区分各部分,注意下标越界问题,其实C++字符串越界几个不会有大问题
ends++;
string part = str.substr(i, ends - i);//获取该部分
i = ends;//每次循环结束有i++,但空格不遍历
switch(part[0]) { //注意两数的计算顺序,重要事情说3遍
case '+':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 + num2);
break;
case '-':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 - num2);
break;
case '*':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 * num2);
break;
case '/':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 / num2);
break;
default:
st_num.push(stod(part));//普通数字直接入栈
}
}
return st_num.top();//栈内只剩一个元素,栈顶即结果
}
逆波兰计算器完整代码如下
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stack>
using namespace std;
string get_suffix(string str) {//中缀转为后缀
stack<string> st_oper;//存储运算符的栈
int len = str.size();
string result;
for(int i = 0; i < len; i++) {
if(str[i] >= '0' && str[i] <= '9') {
int ends = i + 1;
while((str[ends] >= '0' && str[ends] <= '9') || str[ends] == '.')
ends++;
result += str.substr(i, ends - i) + " ";//数字直接输出
i = ends - 1;
} else {
string oper;
oper += str[i];
if(oper == "+" || oper == "-") {//低优先级运算符向前弹栈直到栈空或遇左括号
while(!st_oper.empty() && st_oper.top() != "(") {
result += st_oper.top() + " ";
st_oper.pop();
}
st_oper.push(oper);//别忘了低优先级运算符最后入栈
} else if(oper == "*" || oper == "/" || oper == "(") {//高优先级的运算符直接入栈
st_oper.push(oper);
} else if(oper == ")") {//开始匹配左括号
while(st_oper.top() != "(") {
result += st_oper.top() + " ";//括号之间栈内的运算符输出
st_oper.pop();
}
st_oper.pop();//记得弹出左括号
}
}
}
while(!st_oper.empty()) {
result += st_oper.top();
if(st_oper.size() != 1) {
result += " ";
}
st_oper.pop();
}
return result;
}
double get_result(string str) {//计算后缀表达式获得结果
stack<double> st_num;
int len = str.size();
double num1, num2;
for(int i = 0; i < len; i++) {
int ends = i + 1;
while(ends < len && str[ends] != ' ')//我的后缀式中以空格区分各部分,注意下标越界问题,其实C++字符串越界几个通常不会有大问题
ends++;
string part = str.substr(i, ends - i);//获取该部分
i = ends;
switch(part[0]) { //注意两数的计算顺序,重要事情说三遍
case '+':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 + num2);
break;
case '-':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 - num2);
break;
case '*':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 * num2);
break;
case '/':
num2 = st_num.top();
st_num.pop();
num1 = st_num.top();
st_num.pop();
st_num.push(num1 / num2);
break;
default:
st_num.push(stod(part));//普通数字直接入栈
}
}
return st_num.top();//栈内只剩一个元素,栈顶即结果
}
int main() {
string str;
cout << "输入中缀表达式:";
cin >> str;
str = get_suffix(str);
cout << "后缀表达式为:" << str << endl;
cout << "结果为:" << get_result(str) << endl;
return 0;
}
最后来几张测试图吧,纪念我曾经为该算法逝去的一下午,以上分析权且当作参考,出问题我不负责(¬︿̫̿¬☆)