简单计算器(逆波兰表达式)

简单计算器(逆波兰表达式)

for LeetCode 逆波兰表达式求值 , 基本计算器, 基本计算器II

之前其实在编译原理课上用lex写过一个计算器的小作业,但是对于计算器的universal的实现依然是一知半解,因此就借着做LeetCode自己写了一个比较通用的版本。

我们在离散数学中学过后缀表示法,也叫逆波兰表示法(Reverse Polish notation)。计算机可以很轻松的算出逆波兰表示法下的算式,这也是逆波兰表达式求值这题要求我们写的东西。因此,写一个支持加减乘除括号等操作的简单运算器,最主要的问题是如何将一个算式转化为逆波兰表示。

下面是我处理时的几个小问题

  1. 取负数符号和减号、取正数符号和加号

    这也是基本计算器这一题里面大多数人的问题。 考虑到取负和取正都是一元运算符,这些符号的特征是该符号的前一个有效字符都不是数字或者’)’,而加减号的前一个有效字符必定是数字。 因此我们可以引入一个pre的变量记录上一个有效字符来进行区别。

    注意取正符号其实最后在进行逆波兰计算的时候是不需要计算的,所以我们可以直接忽略掉它。但是取负符号是需要进行转换的,它的优先级介于乘除和加减之间,又因为它的符号和减号一致,但优先级不同,所以我在代码中使用特殊符号‘_’用作取负运算。

  2. 括号的处理

    括号是优先级最高的运算符。其实括号里面都是算式,我们以括号为分界一个算式一个算式的进行转换

  3. 运算符优先级

    从左到右遍历算式时,我们依次将数字push入数据栈,但是符号需要考虑优先级。

    我们人工去检查哪一项先算的时候其实是多看了好几个符号,确认当前符号的下一个符号优先级要小于等于当前符号时,当前符号才可以被执行。因此代码中也是这么体现的:

    当扫描到的当前符号优先级大于符号栈顶符号时,将该符号推入符号栈

    当扫描到的当前符号优先级小于等于符号栈顶符号时,将符号栈中的符号依次推出到数据栈,直到:

    • 栈顶符号为’(’ 或 符号栈为空
    • 栈顶符号的优先级小于当前符号

    然后再将当前符号推入符号栈。

  4. 数据栈从栈底到栈顶的顺序即为逆波兰序

ps: 用到了stack to vector的一个转化方法,看上去很有意思。stack默认的存储方式是deque,但是如果改成vector的话就能比较优雅的实现stack to vector,代价是数据量较大的时候vector实现的stack会有比deque更大的来自reallocation的代价。可以用类似vector<int> a(anothervec.begin(),anothervec.end())的方法将用vector存储的stack转化为vector。 下次有机会要看看vector的具体实现。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <stack>
#include <map>
#include <math.h>
using namespace std;
class Calculater
{
private:
    map<char, int> priority = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'_', 3}, {'^', 4}, {'(', 5}}; // '_'作为取负数的符号与减号做区别
    bool isOperand(char c)
    {
        return (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9');
    }
    //判断是否为运算符
    bool isOpertor(char c)
    {
        return (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c=='^');
    }

    vector<string> Transform2RPN(string &a)
    {
        vector<string> RPN;
        stack<string, std::vector<string>> operand;  // 使用vector来存储而非默认的deque
        stack<char> opertor;
        char pre = '-';
        for (int i = 0; i < a.length(); i++)
        {

            int res = 0;
            while (isOperand(a[i]))
            {
                res = (a[i] - '0') + res * 10;
                if (i + 1 == a.length() || isOperand(a[i + 1]) == false)
                {
                    operand.push(to_string(res));
                    break;
                }
                i++;
            }
            if (isOpertor(a[i]))
            {
                // 负数的符号
                if (a[i] == '-' && (isOpertor(pre) && pre != ')'))
                {
                    a[i] = '_';
                }
                else if (a[i] == '+' && isOpertor(pre) && pre != ')')
                {
                    continue;
                }

                if (a[i] == '(')
                {
                    opertor.push(a[i]);
                }
                else if (a[i] == ')')
                {
                    while (opertor.top() != '(')
                    {
                        char op = opertor.top();
                        opertor.pop();
                        operand.push(string(1, op));
                    }
                    //将'('出栈
                    opertor.pop();
                }
                else
                {
                    if (opertor.empty())
                    {
                        opertor.push(a[i]);
                        continue;
                    }
                    if (opertor.top() == '(')
                    {
                        opertor.push(a[i]);
                        continue;
                    }
                    //(b) 若比运算符堆栈栈顶的运算符优先级高,则直接存入运算符堆栈。
                    if (priority[a[i]] > priority[opertor.top()])
                    {
                        opertor.push(a[i]);
                    }
                    else
                    {
                        // (c) 若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,并将当前运算符压入运算符堆栈。
                        while (!opertor.empty() && opertor.top()!='(' && priority[a[i]] <= priority[opertor.top()])
                        {
                            char op = opertor.top();
                            operand.push(string(1, op));
                            opertor.pop();
                        }
                        opertor.push(a[i]);
                    }
                }
            }
            if (a[i] == ' ')
                continue;
            else
            {
                pre = a[i];
            }
        }
        while (!opertor.empty())
        {
            char op = opertor.top();
            operand.push(string(1, op));
            opertor.pop();
        }
        RPN.assign(&operand.top()+1-operand.size(),&operand.top()+1);  //  优雅的将stack 转换为vector  https://stackoverflow.com/questions/4346010/copy-stdstack-into-an-stdvector
        /*
        while (!operand.empty())
        {
            string op = operand.top();
            RPN.push_back(op);
            operand.pop();
        }
        reverse(RPN.begin(), RPN.end()); */
        return RPN;
    }
    int CalculateRPN(vector<string> &RPN)
    {
        stack<int> s;
        for (int i = 0; i < RPN.size(); i++)
        {
            if (RPN[i] == "+")
            {
                int t = s.top();
                s.pop();
                s.top() += t;
            }
            else if (RPN[i] == "-")
            {
                int t = s.top();
                s.pop();
                s.top() -= t;
            }
            else if (RPN[i] == "*")
            {
                int t = s.top();
                s.pop();
                s.top() *= t;
            }
            else if (RPN[i] == "/")
            {
                int t = s.top();
                s.pop();
                s.top() /= t;
            }
            else if (RPN[i] == "_")
            {
                int t = s.top();
                s.top() = -s.top();
            }
            else if (RPN[i] == "^")
            {
                int t = s.top();
                s.pop();
                s.top() = pow(s.top(),t);
            }
            else
            {
                s.push(stoi(RPN[i]));
            }
        }
        return s.top();
    }

public:
    int calculate(string a)
    {
        vector<string> RPN = Transform2RPN(a);
        return CalculateRPN(RPN);
    }
};

int main()
{
    Calculater c;
    string in;
    cin >> in;
    cout << c.calculate(in) << endl;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于实现逆波兰表达式简单计算器,可以按照以下步骤进行操作: 1. 创建一个来存储操作数。 2. 遍历逆波兰表达式,对于每个元素执行以下步骤: - 如果是数字,将其压入中。 - 如果是操作符,从中弹出两个操作数进行运算,并将结果再次压入中。 3. 最后中的唯一元素就是计算结果。 以下是一个简单的C代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define MAX_STACK_SIZE 100 // 结构定义 typedef struct { int top; int data[MAX_STACK_SIZE]; } Stack; // 初始化 void initialize(Stack* s) { s->top = -1; } // 判断是否为空 int is_empty(Stack* s) { return s->top == -1; } // 判断是否已满 int is_full(Stack* s) { return s->top == MAX_STACK_SIZE - 1; } // 入 void push(Stack* s, int value) { if (is_full(s)) { printf("已满,无法入。\n"); exit(1); } s->data[++s->top] = value; } // 出 int pop(Stack* s) { if (is_empty(s)) { printf("为空,无法出。\n"); exit(1); } return s->data[s->top--]; } // 计算逆波兰表达式 int calculate(char* expression) { Stack s; initialize(&s); int i = 0; while (expression[i] != '\0') { if (isdigit(expression[i])) { push(&s, expression[i] - '0'); } else { int operand1 = pop(&s); int operand2 = pop(&s); switch (expression[i]) { case '+': push(&s, operand1 + operand2); break; case '-': push(&s, operand1 - operand2); break; case '*': push(&s, operand1 * operand2); break; case '/': push(&s, operand1 / operand2); break; default: printf("非法操作符:%c\n", expression[i]); exit(1); } } i++; } int result = pop(&s); if (!is_empty(&s)) { printf("逆波兰表达式不合法。\n"); exit(1); } return result; } int main() { char expression[] = "34+56-*"; int result = calculate(expression); printf("计算结果:%d\n", result); return 0; } ``` 此代码中的逆波兰表达式为"34+56-*",计算结果为14。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值