C++利用数组模拟栈进行中缀表达式求值--附源码 中缀表达式算法

我们都知道后缀表达式是很好算的,但是如何进行中缀表达式的求值?

如何计算中缀表达式?


一、中缀表达式是什么?

中缀表达式(或中缀记法)是一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间(例:3 + 4),中缀表达式是人们常用的算术表示方法。

以上来自百度百科
其实说白了,中缀表达式就是我们日常生活中所用的算式。
比如 1 + 1 * 2 / (1 + 2)就是一个中缀表达式

二、问题分析

为简化问题起见,我们这里仅仅考虑表达式中的数均为非负数,运算包含 + 、- 、 * 、/四种基本运算,另外,表达式中可能出现括号。

1.模拟
我们来考虑一个具体的实例,以计算机的视角来看,我们只能从左向右遍历表达式,对于当前遍历位置之后的表达式我们是不得而知的。

考虑 3 * 5 + 2
从左向右遍历:

  1. 遇到数字 3什么都做不了先存储
  2. 遇到 *,考虑可能要进行乘法,依然不能确定后边有无括号,先存储
  3. 遇到 5,这时候可以直接乘吗?还是 先存储
  4. 遇到 +,考虑前一个运算是乘法,这时我们可以笃定上一个运算一定是 3 * 5
  5. 如果当前位是 * 或者 /,我们依然有上面的结论

由此我们得到了第一个结论

结论1 : 遍历到一个运算符时,如果上一个运算符(与当前运算符间无括号)的优先级大于等于当前运算符,那么在表达式里,我们一定先算上一个运算符

2.有括号的情况

考虑 3 * (5 + 2)
从左向右遍历:

  1. 遇到数字 3什么都做不了先存储
  2. 遇到 *,考虑可能要进行乘法,依然不能确定后边有无括号,先存储
  3. 遇到 (,后面一定有一个有括号和它对应,先存储
  4. 遇到 5,说明它是括号表达式的一部分,还是 先存储
  5. 遇到 +,前一个运算是乘法,这时我们可以笃定上一个运算一定是 3 * 5吗???不行,因为这里多出来一个拦路虎 (
  6. 我们可以看出,括号表达式相当于把原表达式截取了一部分,这一部分和其他部分是相互独立的,因此,我们在遍历到一个右括号时,要把 上一个出现的左括号和它之间的部分取出来进行计算!!!

于是我们得到了结论2

结论2 : 当前位为左括号,直接储存等待和右括号配对;当前位为右括号,读取到上一个左括号之后的所有运算符和数字进行计算

到这里,聪明的读者已经发现,我们应该使用的数据结构为 : 

具体实现思路如下:

中缀表达式求值
方法:开两个栈,操作符栈和数组栈
从左到右扫描中缀表达式

  1. 当前位为数字:继续向后扫描直到读取完整个数字,将其放入数字栈中
  2. 当前位为左括号,直接放入操作符栈中等待和右括号配对
  3. 当前位为右括号,从操作符栈中读取到上一个左括号之后的所有运算符,
    每读取一个就取数字栈中两个元素进行计算,并把结果放入栈中
  4. 当前位为运算符,如果栈顶元素优先级更高或者和当前运算符相等,就弹出
    栈顶操作符,并进行计算,知道栈顶元素为左括号或者栈顶<当前或者栈为空
    遍历结束表达式后,将剩余的操作符进行计算

三、C++实现

#include<iostream>
using namespace std;
char op[100010]; //stack of operators
long long nums[100010]; //stack of numbers
int t1 = -1,t2 = -1;
bool isdigit(char ch)
{
    return ch >= '0' && ch <= '9';
}
int compare(char a, char b)
{
    if((a == '+' || a == '-') && (b == '*' || b ==
    '/'))return -1;
    else if((b == '+' || b == '-') && (a == '*' || a ==
    '/'))return 1;
    return 0;
}
void calc(char ch)
{
    long long b = nums[t2--], a = nums[t2 --];
    if(ch == '+') nums[++t2] = a + b;
    else if(ch == '-')nums[++t2] = a - b;
    else if(ch == '*')nums[++t2] = a * b;
    else if(ch == '/')nums[++t2] = a / b;
}
int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    string s;
    cin >> s;
    for(int i = 0;i < s.size();i ++){
        char x = s[i];
        if(isdigit(x)){
            long long num = x - '0';
            while(i < s.size() && isdigit(s[++i]))num = num * 10 + s[i] - '0';
            i--;
            nums[++ t2] = num;
        }
        else if(x == '(')op[++ t1] = x;
        else if(x == ')'){
            char ch = op[t1--];
            while(ch == '+' || ch == '*' || ch == '-' || ch == '/'){
                calc(ch);
                ch = op[t1--];
            }
        }
        else{
            char ch = op[t1];
            if(ch == '('){
                op[++ t1] = x;
                continue;
            }
            while(t1 != -1 && ch != '(' && compare(x,ch) != 1 )
            {
                t1--;
                calc(ch);
                ch = op[t1];
                if(ch == '('){
                    break;
                }
            }
            op[++ t1] = x;
        }
    }
    while(t1 != -1)
    {
        char ch = op[t1 --];
        calc(ch);
    }
    cout << nums[t2];
    return 0;
}

由于每个元素最多入栈出栈一次,因此时间复杂度为 O(n)
空间复杂度为 O(n)

总结

限于笔者水平,如有纰漏还请见谅。

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值