c++后缀表达式实现eval详解(附代码)

后缀表达式又称为逆波兰表达式,在计算机解析算式字符串上有很大的应用。比如大家熟知的PythonJavaScript中的eval()函数,可以很方便地计算算式字符串的值:

>>> infix = "1+(2+4)*8"
>>> eval(infix)
49

这个强大的功能便是通过后缀表达式实现的,下面,我们可以在C++中实现这个函数:
首先导入需要的库:

#include <iostream>
#include <stdio.h>
#include <sstream>
#include <string>
#include <vector>
#include <map>
using namespace std;

然后,声明几个全局变量并实现几个简单的函数:

map<string, int> op_mapping;    // 运算符优先级映射表
string ops = "+-*/()";                  // 包含所有运算符的字符串,为了通过find方法快速判断目标字符是否为操作符
bool ValueError = false;               // 是否发生数值错误的标记
bool ExpressionError = false;      // 是否发生表达式错误的标记

// 将string转换成double
double toDouble(string str)
{
    double target;
    stringstream ss;
    ss << str;
    ss >> target;
    return target;
}

// 初始化操作符优先级映射表
void init_mapping(map<string, int> &mapping)
{
    mapping["+"] = 0;
    mapping["-"] = 0;
    mapping["*"] = 1;
    mapping["/"] = 1;
    mapping["("] = 2;
    mapping[")"] = 2;
}

init_mapping()和变量op_mapping是为了快速比较两个运算符的优先级大小。当然,如果你不是通过这个方法来比较运算符优先级的话,你完全可以不这么写。


中缀表达式转后缀表达式

接下来,就是核心部分的编写了,首先,我们需要将中缀表达式转为后缀表达式。相信学过数据结构的读者应该对这个逻辑不陌生,中缀表达式转为后缀表达式是通过栈来完成的(也可以通过树)。我们需要两个栈,存放最终结果的result和临时存放操作符的op_stack

基本过程为,从左到右遍历中缀表达式的每个字符,通过ops.find()我们可以快速判别这个字符代表操作数还是操作符,如果是操作数,则将这个数字push_back()进结果栈result;如果是操作符,记为cur_op,则进行以下判断:

  • op_stack为空,则cur_op直接入栈。
  • cur_op=="(",则cur_op直接入栈。
  • cur_op==")",则进行如下操作:开始将op_stack中的元素弹出,并将弹出的元素放入result中,直到弹出的元素为(,停止弹出,并将弹出的(与当前元素cur_op(也就是))丢弃。
  • op_stack.back()=="("cur_op不为括号,则cur_op直接入栈。
  • cur_opop_stack.back()都不为括号的情况下,cur_op的优先级大于op_stack.back(),则cur_op直接入栈。
  • cur_opop_stack.back()都不为括号的情况下,cur_op的优先级小于等于op_stack.back(),则将op_stack中元素逐一弹出,直到cur_op的优先级大于op_stack的栈顶元素,或者栈内为空,停止弹出,cur_op入栈。

中缀表达式遍历完后,将op_stack中的剩余元素逐一弹出,并push_back()result

举个简单的例子,我们目前的中缀表达式如下:

infix expression=5+(1+2)∗4−3

下面演示一下手动中缀转后缀:

潦草警告=_=

 

 

 

 

 

 

 

 

12.中缀表达式遍历结束,将op_stack中剩余元素逐一移到result中,再将元素从栈底逐个读出,得到的便是后缀表达式了:

postfix expression=5 1 2 + 4 ∗ + 3 − 

当然,为了程序的稳健性,我们需要在多个节点做判断。比如如果用户输入的是一个非法的中缀表达式5 + 1 + 2 ) ∗ 4 − 3 5+1+2)*4-35+1+2)∗4−3,少了左括号,那么在读入)时,程序会将元素逐个弹出,直到栈顶元素为(。因此,一旦扫到(,则会导致栈内所有操作符被弹出,继而出错。所以我们需要在一些关键节点做判断,尤其是在使用.back()方法获取栈顶元素时,一定要在可能报错的地方判断栈是否为空。

除此之外,扫描数字的逻辑也值得注意。我们上面的数字都是一个字符就可以表示,因此,逐一扫描数字,再push_back()进result中没错,但是一旦我们的操作数出现了34,251这样字符串时,读到一个字符就直接push_back()进result中就不对了,我们需要一个string cur_num来临时保存读到字符,等到251读全了,再将cur_num push_back()进result,然后清空cur_num就行了。
程序如下: 

// 将中缀表达式转化为后缀表达式
vector<string> toPostfix(string formula)
{
    vector<string> result;
    vector<string> op_stack;
    string cur_num, cur_op;

    for (int i = 0; i < formula.size(); ++ i)
    {
        if (ops.find(formula[i]) == ops.npos)   // 扫描到的是操作数
        	cur_num += formula[i]; 
			
        else        // 扫描到的是操作符,现将累加的操作数字符串加入
        {
            if (!cur_num.empty())
            {
                 result.push_back(cur_num);
                cur_num.clear();
            }
           
            cur_op = formula[i];

            if (op_stack.empty())           // 栈为空,直接入栈
                op_stack.push_back(cur_op);
            else if (cur_op == "(")         // 当前操作数为左括号,直接入栈
                op_stack.push_back(cur_op);
            else if (cur_op == ")")         // 当前操作数为右括号,则需要将op_stack中直到左括号前的所有的元素弹出
            {
                while (op_stack.back() != "(")
                {                   
                    result.push_back(op_stack.back());
                    op_stack.pop_back();

                    if (op_stack.empty())           // 不合法的表达式会出现这样的情况
                    {
                        ExpressionError = true;
                        result.push_back("0");
                        return result;
                    }
                }
                op_stack.pop_back();        // 将左括号弹出
            }
            else if (op_stack.back() == "(")          // 在当前操作符不是括号的情况下,如果栈顶元素为左括号,则直接入栈
                op_stack.push_back(cur_op);
            else if (op_mapping[cur_op] > op_mapping[op_stack.back()])        // 在当前操作符和栈顶元素为+-*/的情况下,若当前操作符优先级大于栈顶元素,直接入栈
                op_stack.push_back(cur_op);
            else            // 最后一种情况就是当前操作符的优先级低于或等于栈顶元素优先级时
            {
                while ((op_stack.back() != "(") && (op_mapping[op_stack.back()] >= op_mapping[cur_op]))
                {
                    result.push_back(op_stack.back());
                    op_stack.pop_back();
                    // 若栈已空,则直接返回
                    if (op_stack.empty())
                        break;
                }
                op_stack.push_back(cur_op);     // 符合要求的操作符弹出后,当前操作符入栈
            }
        }
    }

    result.push_back(cur_num);

    // 最后op_stack可能还会有剩余元素,全部弹出
    while(!op_stack.empty())
    {
        result.push_back(op_stack.back());
        op_stack.pop_back();
    }

    return result;
}

 

计算后缀表达式

计算后缀表达式就简单了,我们之所以将中缀表达式转换成后缀表达式就是因为后缀表达式好算。后缀表达式没有括号,运算符优先级体现在了自身结构上。

具体计算很简单:我们建一个栈result。然后从左往右扫描后缀表达式,遇到操作数,就入栈;遇到操作符op,就让栈顶的两个元素num1和num2出栈,假设在栈中时num1在num2的上面,则计算num2 op num1,将所得结果入栈。最终栈内只会剩下一个元素,这个元素便是答案。

很显然,如果原本输入的中缀表达式不合法,则会出现两种隐患:

  • 在获取num1num2时,若栈内元素不足两个,会出错。
  • 最终得到的result中的元素数量大于1。

我们可以在这些节点上特判。程序如下:

double calculatePostfix(vector<string> &postfix)
{
    vector<double> result;
    for (int i = 0; i < postfix.size(); ++ i)
    {
        if (ops.find(postfix[i]) == ops.npos)        // 扫描到操作数,直接压入栈中
            result.push_back(toDouble(postfix[i]));
        else               // 扫描到操作符
        {
            // 如果剩余元素的数量小于2,则表达式非法
            if (result.size() < 2)
            {
                ExpressionError = true;
                return 0.0;
            }
            double num1 = result.back();
            result.pop_back();
            double num2 = result.back();
            result.pop_back();
            double op_res;
            // 分类讨论,计算num2 op num1
            if (postfix[i] == "+")
                op_res = num2 + num1;
            else if (postfix[i] == "-")
                op_res = num2 - num1;
            else if (postfix[i] == "*")
                op_res = num2 * num1;
            else if (postfix[i] == "/")
            {
                // 此处需要判断一下是否分母为0
                if (num1 == 0)
                {
                    ValueError = true;
                    return 0.0;
                }
                   
                op_res = num2 / num1;
            }
            // 将所的结果重新压入栈中
            result.push_back(op_res);
        }
    }
    if (result.size() == 1)     // 返回栈顶元素(如果是合法的表达式,则此时栈中只有一个元素)
        return result.back();       
    else            // 不合法的表达式会导致结束时,result中有不止一个元素
    {
        ExpressionError = true;
        return 0.0;
    }
}

拼出最后的eval()函数

将得到后缀表达式的函数和计算后缀表达式的函数一整合就可以得到eval()函数了: 

double eval(string &infix)
{
    vector<string> postfix = toPostfix(infix);
    return calculatePostfix(postfix);
}

最后我们可以测试一下:

int main()
{
    double result;
    init_mapping(op_mapping);       // 初始化映射表
    string infix = "1+2*(3+4)-5";
    cout << eval(infix);
    return 0;
}

输出结果为:

10

符合预期。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浪子小院

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值