C++篇(13)计算器实现

1、计算器实现思路

我们日常写的计算表达式都是中缀表达式,也就是运算符在中间,运算数在两边,但是直接读取无法马上进行计算,因为一个计算表达式还涉及运算符优先级的问题。比如:1-2*(3-4)+5 中遇到-和*都无法运算,因为后面还有括号,优先级更高。

所以其中一种实现思路就是把中缀表达式转换为后缀表达式 ,也就是分析计算表达式的优先级,将运算数放在前面,运算符放到运算数后面,然后我们依次读取后缀表达式,遇到运算符就可以进行计算了。后缀表达式也被称为逆波兰表达式(Reverse Polish Notation,RPN)

2、后缀表达式进行运算

https://leetcode.cn/problems/evaluate-reverse-polish-notation/submissions/671060134

后缀表达式因为已经确定好了优先级,运算方式非常简单,就是遇到运算符时,取前面两个运算数进行运算,因为经过中缀转后缀,优先级已经确立好了。

建立一个栈存储运算数,读取后缀表达式,遇到运算数入栈遇到运算符,出栈顶的两个数据进行运算,运算后将结果作为一个运算数入栈,继续参与下一次运算。读取表达式结束之后,最后栈里面的值就是运算结果。

cpp 复制代码
class Solution
{
public:
	int evalRPN(vector<string>& tokens)
	{
		stack<int> st;
		
		for(auto& str : tokens)
		{
            //str为数字
			if((str != "+") && (str != "-") && (str != "*") && (str != "/"))
			{
				st.push(stoi(str));
			}
			else
			{
                //str为操作符
				int right = st.top();
				st.pop();

				int left = st.top();
				st.pop();
				
				switch(str[0])
				{
					case '+' : 
						st.push(left + right);
						break;
					case '-' :
						st.push(left - right);
						break;
					case '*' :
						st.push(left * right);
						break;
					case '/' :
						st.push(left / right);
						break;
				}
			}
		}
		
		return st.top();
	}	
};

3、中缀表达式转后缀表达式

依次读取计算表达式中的值,遇到运算数直接输出。

建立一个栈存储运算符,利用栈后进先出的性质,遇到后面的运算符,出栈里面存的前面运算符进行比较,确定优先级。

遇到运算符,如果栈为空 或者 栈不为空并且当前运算符比栈顶运算符优先级高,则当前运算符入栈。因为如果栈里面存储的是前一个运算符,当前运算符比前一个运算符优先级高,说明前一个不能运算,当前运算符也不能运算,因为后面可能还有更高优先级的运算符。

遇到运算符,如果栈不为空并且当前运算符比栈顶运算符优先级低,说明栈顶的运算符可以运算了,则输出栈顶运算符,当前运算符继续走前面遇到运算符的逻辑。

如果遇到(),则把括号的计算表达式当成一个子表达式,跟上面思路类似,进行递归处理子表达式,处理后转换出的后缀表达式加在前面表达式的后面即可。

计算表达式或者()中子表达式结束值,输出栈中所有运算符。
示例1 示例2 ① 示例2 ② 示例2 ③

cpp 复制代码
class Solution
{
public:
	map<char, int> operatorPrecedence = {{'+',1}, {'-',1}, {'*',2}, {'/',2}};
	
	//中缀转后缀
	void toRPN(const string& s, size_t& i, vector<string>& v)
	{
		stack<char> st;
		
		while(i < s.size())
		{
			if(isdigit(s[i]))
			{
				// 操作数直接输出
				string num;
				while(i < s.size() && isdigit(s[i]))
				{
					num += s[i];
					++i;
				}
				v.push_back(num);
			}
			else if(s[i] == '(')
			{
				// 子表达式,递归处理即可
				++i;
				toRPN(s, i, v);
			}
			else if(s[i] == ')')
			{
				// 子表达式结束
				// 输出栈里面的剩余运算符
				while(!st.empty())
				{
					v.push_back(string(1, st.top()));
					st.pop();
				}
				++i;
				return;
			}
			else 
			{
				// 运算符
				if(st.empty() || (operatorPrecedence[st.top()] < operatorPrecedence[s[i]]))
				{
					st.push(s[i]);
					++i;
				}
				else
				{
					char op = st.top();
					st.pop();
					
					v.push_back(string(1, op));
				}
			}
		}
		// 表达式结束
		// 输出栈里面的剩余运算符
		while(!st.empty())
		{
			v.push_back(string(1, st.top()));
			st.pop();
		}
	}
};

4、计算器的实现

https://leetcode.cn/problems/basic-calculator

有了上面两个部分,计算器OJ的大部分问题就解决了,但是这里还有一些问题需要处理。因为OJ中给的中缀表达式是字符串,且字符串中包含空格,需要去掉空格。

cpp 复制代码
int calculate(string s) 
{
    //去掉空格
    string news;
    for(auto& ch : s)
    {
        if(ch != ' ')
        {
            news += ch;
        }
    }

    size_t i = 0;
    vector<string> v;
    toRPN(news, i, v);

    return evalRPN(v);
}

那是不是这样就行了呢?提交代码之后,提示我们有测试样例未通过。

可以发现,这里还需要区分负数和减号。如果是减号的话,那么前面一个是操作数;如果是负号的话,前面一个是操作符。我们需要将所有的负数-x 转换为 0-x。

cpp 复制代码
int calculate(string s) 
{
    //去掉空格
    string news;
    for(auto& ch : s)
    {
        if(ch != ' ')
        {
            news += ch;
        }
    }

    //处理负数
    news.swap(s);
    news.clear();
    for(size_t i = 0;i < s.size();i++)
    {
        if(s[i] == '-' && !isdigit(s[i - 1]))
        {
            news += "0-";
        }
        else
        {
            news += s[i];
        }
    }

    size_t i = 0;
    vector<string> v;
    toRPN(news, i, v);

    return evalRPN(v);
}

提交之后发现仍然有测试样例不通过。

说明我们判断条件是不够的,还需要判断减号前面是不是右括号。

cpp 复制代码
int calculate(string s) 
{
    //去掉空格
    string news;
    for(auto& ch : s)
    {
        if(ch != ' ')
        {
            news += ch;
        }
    }

    //处理负数
    news.swap(s);
    news.clear();
    for(size_t i = 0;i < s.size();i++)
    {
        if(s[i] == '-' && (!isdigit(s[i - 1]) && s[i - 1] != ')'))
        {
            news += "0-";
        }
        else
        {
            news += s[i];
        }
    }

    size_t i = 0;
    vector<string> v;
    toRPN(news, i, v);

    return evalRPN(v);
}

再提交一下。。。

又有测试样例崩溃了,因为这里是s[0]的位置出现-号,因此s[i-1]就会越界。所以这个地方还要加一个条件。

完整题解代码如下:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        for(int i = 0;i < tokens.size();i++)
        {
            string& str = tokens[i];

            if((str != "+") && (str != "-") && (str != "*") && (str != "/"))
            {
                st.push(stoi(str));
            }
            else
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                switch(str[0])
                {
                    case '+':
                        st.push(left + right);
                        break;
                    case '-':
                        st.push(left - right);
                        break;
                    case '*':
                        st.push(left * right);
                        break;
                    case '/':
                        st.push(left / right);
                        break;
                }
            }
        }

        return st.top();
    }

    map<char, int> operatorPrecedence = { {'+',1},{'-',1},{'*',2},{'/',2} };
    //中缀转后缀
    void toRPN(const string& s, size_t& i, vector<string>& v)
    {
        stack<char> st;

        while (i < s.size())
        {
            //操作数直接输出
            if (isdigit(s[i]))
            {
                string num;
                while (i < s.size() && isdigit(s[i]))
                {
                    num += s[i];
                    i++;
                }

                v.push_back(num);
            }
            else if (s[i] == '(')
            {
                //子表达式,递归处理即可
                i++;
                toRPN(s, i, v);
            }
            else if (s[i] == ')')
            {
                //子表达式结束
                //输出栈里面的剩余运算符
                while (!st.empty())
                {
                    v.push_back(string(1, st.top()));
                    st.pop();
                }
                i++;
                return;
            }
            else
            {
                //运算符
                if (st.empty() || operatorPrecedence[s[i]] > operatorPrecedence[st.top()])
                {
                    st.push(s[i]);
                    i++;
                }
                else
                {
                    char op = st.top();
                    st.pop();

                    v.push_back(string(1, op));
                }
            }
        }
        //表达式结束
        //输出栈里面的剩余运算符
        while (!st.empty())
        {
            v.push_back(string(1, st.top()));
            st.pop();
        }
    }

    int calculate(string s) 
    {
        //去掉空格
        string news;
        for(auto& ch : s)
        {
            if(ch != ' ')
            {
                news += ch;
            }
        }

        //处理负数
        news.swap(s);
        news.clear();
        for(size_t i = 0;i < s.size();i++)
        {
            if(s[i] == '-' && (i == 0 || (!isdigit(s[i - 1]) && s[i - 1] != ')')))
            {
                news += "0-";
            }
            else
            {
                news += s[i];
            }
        }

        size_t i = 0;
        vector<string> v;
        toRPN(news, i, v);

        return evalRPN(v);
    }
};

5、其他实现思路

https://leetcode.cn/problems/basic-calculator/solutions/646369/ji-ben-ji-suan-qi-by-leetcode-solution-jvir

其实一句话就可以解析:就是去括号的思路,比如1-(1+2)=1-1-2,有乘法和除法类似,比考虑中缀转后缀的思路简单。

cpp 复制代码
class Solution {
public:
    int calculate(string s) 
    {
        stack<int> ops;
        ops.push(1);
        int sign = 1;

        int ret = 0;
        int n = s.length();
        int i = 0;
        while (i < n) 
        {
            if (s[i] == ' ') 
            {
                i++;
            } 
			else if (s[i] == '+') 
			{
                sign = ops.top();
                i++;
            } 
			else if (s[i] == '-') 
			{
                sign = -ops.top();
                i++;
            } 
			else if (s[i] == '(') 
			{
                ops.push(sign);
                i++;
            } 
			else if (s[i] == ')') 
			{
                ops.pop();
                i++;
            } 
			else 
			{
                long num = 0;
                while (i < n && s[i] >= '0' && s[i] <= '9') 
				{
                    num = num * 10 + s[i] - '0';
                    i++;
                }
                ret += sign * num;
            }
        }
        return ret;
    }
};

https://leetcode.cn/problems/basic-calculator-ii

https://leetcode.cn/problems/basic-calculator-ii/solutions/648647/ji-ben-ji-suan-qi-ii-by-leetcode-solutio-cm28

cpp 复制代码
class Solution {
public:
    int calculate(string s) 
	{
        vector<int> stk;
        char preSign = '+';
        int num = 0;
        int n = s.length();
        for (int i = 0; i < n; ++i) 
		{
            if (isdigit(s[i])) 
			{
                num = num * 10 + int(s[i] - '0');
            }
            if (!isdigit(s[i]) && s[i] != ' ' || i == n - 1) 
			{
                switch (preSign) 
				{
                    case '+':
                        stk.push_back(num);
                        break;
                    case '-':
                        stk.push_back(-num);
                        break;
                    case '*':
                        stk.back() *= num;
                        break;
                    default:
                        stk.back() /= num;
                }
                preSign = s[i];
                num = 0;
            }
        }
        return accumulate(stk.begin(), stk.end(), 0);
    }
};
相关推荐
CHEN5_023 小时前
【leetcode100】和为k的子数组(两种解法)
java·数据结构·算法
Codeking__3 小时前
DFS算法原理及其模板
算法·深度优先·图论
Victory_orsh4 小时前
“自然搞懂”深度学习系列(基于Pytorch架构)——01初入茅庐
人工智能·pytorch·python·深度学习·算法·机器学习
88号技师4 小时前
2025年8月SCI-汉尼拔·巴卡优化算法Hannibal Barca optimizer-附Matlab免费代码
开发语言·人工智能·算法·数学建模·matlab·优化算法
_w_z_j_4 小时前
C++----变量存储空间
开发语言·c++
龙腾AI白云4 小时前
大模型-高效优化技术全景解析:微调 量化 剪枝 梯度裁剪与蒸馏 上
算法
地平线开发者4 小时前
新版 perf 文件解读与性能分析
算法·自动驾驶
lingran__4 小时前
算法沉淀第五天(Registration System 和 Obsession with Robots)
c++·算法
chrispang4 小时前
浅谈 Tarjan 算法
算法