前言
之前学数据结构的时候用栈写四则运算不太会,觉得网上的代码和课本里的逻辑很怪,比较难理解。但这个又很经典,据说面试还可能会考到。这里某天有灵感的时候就记录下来了。如果是学了编译原理的文法规则朋友可以看下这个: 使用文法规则实现四则运算 (加减乘除括号)
源代码
先上源代码,再来解释具体的细节。
//
// Created by Andy Dennis on 2020/9/9.
//
# include <iostream>
# include <stack>
# include <cmath>
using namespace std;
int getPriority(char op);
float cal_one(float n1, char op, float n2);
float cal(const string& s)
{
string str = s;
float n1, n2, tn = 0;
int lastPriority = 0, priority;
char op;
stack <float> numS;
stack <char> opS;
float result = 0;
for(int i = 0; i < s.length(); i++){
// 数字
if (str[i] >= '0' && str[i] <= '9'){
tn = tn * 10 + float(str[i] - '0');
} else if (str[i] == ' '){
continue;
} else {
numS.push(tn);
priority = getPriority(str[i]);
if (priority <= lastPriority){ // 优先级一样或者低于上一个操作符
while (!opS.empty()){
op = opS.top();
lastPriority = getPriority(op);
if (lastPriority >= priority) {
opS.pop();
n2 = numS.top();
numS.pop();
n1 = numS.top();
numS.pop();
n1 = cal_one(n1, op, n2);
numS.push(n1);
} else {
// 避免死循环
break;
}
}
opS.push(str[i]);
} else { // 优先级高于上一个
opS.push(str[i]);
}
tn = 0;
lastPriority = priority;
}
}
//把最后一个操作数也加上
numS.push(tn);
while (!opS.empty()){
op = opS.top();
opS.pop();
n2 = numS.top();
numS.pop();
n1 = numS.top();
numS.pop();
result = cal_one(n1, op, n2);
numS.push(result);
}
return result;
}
int getPriority(char op)
{
// -1 代表没有找到对应运算符
int priority = -1;
if (op == '+' || op == '-')
priority = 1;
else if (op == '*' || op == '/')
priority = 2;
else if (op == '^')
priority = 3;
return priority;
}
float cal_one(float n1, char op, float n2){
float result = 0;
switch (op) {
case '+':
result = n1 + n2;
break;
case '-':
result = n1 - n2;
break;
case '*':
result = n1 * n2;
break;
case '/':
result = n1 / n2;
break;
case '^':
result = pow(n1, n2);
break;
}
return result;
}
int main(){
cout << "2 ^ 3 + 5 * 8 - 3 / 2 = " << cal("2 ^ 3 + 5 * 8 - 3 / 2") << endl;
return 0;
}
这里相信大家都已经看出来,getPriority就是获取操作符优先级的,cal_one就是获取某个式子的正则运算的。关键部分在cal函数里面。
- cal函数的主要思想是,先遍历字符串的一个个字符,识别出数字(中途有空格都无所谓,因为加了个else if(str[i) == ’ ')就直接continue了), 然后遇到操作符就应该考虑和上一个字符的优先级,如果优先小于等于上一个,那么此时上一个操作符就可以从栈里拿出来运算了。然后在判断上上个操作符,以此类推,这里使用STL中的stack会比较方便。最后记得把最后一个操作数加进去,然后把最后的运算给迭代运算完。
- 为什么最后可以直接出栈运算呢?
因为按照我们的思路,在栈里面的优先级都是从栈底到栈顶递增的,此时按出栈运算刚好是从栈内的优先级高的先运算,保证了算法的正确性。
运行结果:
扩展1: 支持输入是浮点数
这个由于时间关系,我就不去全部写出来了,不过这里有个例子可以供大家参考。
# include <iostream>
# include <cmath>
using namespace std;
float string2float(string s)
{
float n = 0;
bool isXiaoShu = false; // 判断当前的数字是不是小数部分
int xiaoShuLength = 0; // 记录小数部分的长度
for (int i = 0; i < s.length(); i++){
if (s[i] >= '0' && s[i] <= '9'){
n = n * 10 + float (s[i] - '0');
if (isXiaoShu)
xiaoShuLength++;
}
else if (s[i] == '.'){
isXiaoShu = true; //开始进入小数部分
}
}
return n * 1.0 / pow(10, xiaoShuLength);
}
int main()
{
float num = string2float("4.28");
cout << num;
}
运行结果:
扩展2: 支持括号运算
这里我给个思路,就是我们可以把运算符的权重定义为 priority + bracketPrioriy,
- 其中,priority和我们之前说的完整代码里的priority一样
- bracketPrioriy呢, 每次我们检测到 左括号 ( 的时候,我们就让bracketPrioriy += 10(bracketPrioriy初始为0, 且这里的10不是固定的,只要足够让括号里的运算符大于括号外的运算符即可),然后遇到 ) 的时候,我们就让bracketPrioriy -= 10,并且迭代出栈运算,一直等到出栈的运算符是 ( , 其他与完整代码的例子的思路一样。
该算法的实例
我在另一篇文章中用kotlin实现了加减乘除运算, 写成了一个app。
链接: android计算器(kotlin实现)
实现四则运算其他思路
这两个例子包括了加减乘除,还有括号运算。