栈如何实现计算器

计算器

用栈实现计算器是一种非常常见的算法题,我们先来看下面这道题:

简单来讲就是给你一个算式字符串,通过算法输出运算结果,这里没有涉及到带括号的问题,就是简单加减乘除运算。首先你要搞清楚给我们的是字符串,我们要是想加减运算就要先给他转成数字。这里实现起来很简单,遍历数组,只要没有遍历到符号就要将数字提取出来,一位数还好是他本身,如果是两位或者三位数就要采取一些运算,这里我是这样写的num = num * 10 + c - '0'。这里的c是遍历字符串时单个字符,num初始值是0,表示转换后的数字。有人会疑问为啥要c-'0'?我们要注意c是字符不是int数字,字符 '0'的编码值为 48, 而字符 '1' 到 '9' 的编码值为 49 到 57。因此,将字符数字减去字符 '0'就可以将其转换为对应的数字值。

好,搞定了数字的处理,下面就要考虑运算顺序了,当+和*同时存在时如何保证先算*。由于乘除优先于加减计算,因此不妨考虑先进行所有乘除运算,并将这些乘除运算后的整数值放回原表达式的相应位置,则随后整个表达式的值,就等于一系列整数加减后的值。

基于此,我们可以用一个栈,保存这些(进行乘除运算后的)整数的值。对于加减号后的数字,将其直接压入栈中;对于乘除号后的数字,可以直接与栈顶元素计算,并替换栈顶元素为计算后的结果。

具体来说,遍历字符串,并用变量preSign记录每个数字之前的运算符,对于第一个数字,其之前的运算符视为加号。每次遍历到数字末尾时,根据preSign来决定计算方式:

加号:将数字压入栈;减号:将数字的相反数压入栈;乘除号:计算数字与栈顶元素,并将栈顶元素替换为计算结果。代码实现中,若读到一个运算符,或者遍历到字符串末尾,即认为是遍历到了数字末尾。处理完该数字后,更新preSign为当前遍历的字符。

代码如下:

public int calculate(String s) {
Deque<Integer> stack = new ArrayDeque ();
int len=s.length();
char proSign='+';
int num=0;
for(int i=0;i<len;i++){
	char c = s.charAt(i);
	if(Character.isDigit(c)){
		num = num * 10 + c - '0' ;
	}
	if(!Character.isDigit(c)&&c!=' '|| i==len-1){
		if(proSign=='*'){
			stack.push(stack.pop() * num);
		}else if(proSign=='/'){
			stack.push(stack.pop() / num);
		}else if(proSign=='-'){
			stack.push(-num);
		}else {
			stack.push(num);
		}
		proSign=c;
		num=0;
	}
}
int ans=0;
while(!stack.isEmpty()){
	ans+=stack.pop();
}
return ans;
}

当然这种情况下我们只能处理简单的加减乘除,如果有括号或者较为复杂的括号嵌套,这种方法可就不行了。关于这类情况就不得不提到我们下面要说的波兰表达式了。

波兰表达式

什么是波兰表达式?

我们先理解一下什么是表达式,表达式就是小学里学的类似(2+1)*3)这样的式子,根据不同的记法,有前缀、中缀和后缀三种方式,其区别在于运算符相对于操作数的位置,前缀表达式的运算符位于操作数之前,中缀和后缀同理,如下图,其实这就对应了树的前中后三种遍历方式。如图:

对应了三种表达式:

  • 中缀表达式:1+(2+3)×4-5
  • 前缀表达式:-+1×+2345
  • 后缀表达式:123+4×+5-

我们可一看到好像就中缀表达式我们能看懂,其他都是啥啊?当然,前缀和后缀是给计算机看的,如果我们直接将中缀式给计算机他处理起来会很麻烦,我们就可以将中缀式转换层前缀或者后缀式,这样就方便多了(具体我们待会讲)。我们通常将前缀式叫做波兰式,后缀时叫做逆波兰式,这就是波兰式的由来。

下面是转换方式,感兴趣的可以看一下

  1. 中缀式转后缀式
  • 从左到右扫描中缀式,遇到数值时直接输出。
  • 遇到运算符时,若其优先级高于栈顶运算符,则将其压入栈中;否则,将栈顶运算符弹出并输出,重复此操作,直到运算符的优先级低于栈顶运算符为止。最后将运算符入栈。
  • 当扫描到中缀式的末尾时,将栈中所有运算符弹出并输出。

例如,中缀式表达式 2 + 3 * 4 - 5,其对应的后缀式为 2 3 4 * + 5 -。

  1. 中缀式转前缀式
  • 与中缀式转后缀式类似,从右至左扫描中缀式。
  • 遇到数值时直接输出。
  • 遇到运算符时,若其优先级低于栈顶运算符,则将其压入栈中;否则,将栈顶运算符弹出并输出,重复此操作,直到运算符的优先级高于栈顶运算符为止。最后将运算符入栈。
  • 当扫描完成时,将栈中所有运算符依次弹出并输出即为前缀式。

例如,中缀式表达式 2 + 3 * 4 - 5,其对应的前缀式为 - + 2 * 3 4 5。

  1. 后缀式转中缀式

要将后缀式转换为中缀式,可以从左到右扫描后缀式,遇到数值时将其压入栈中,遇到运算符时将栈顶的两个数值弹出并计算,并将运算符插入两个数值之间,最后输出栈中的数值即可。例如,后缀式表达式 2 3 4 * + 5 - 对应的中缀式为 2 + 3 * 4 - 5。

  1. 后缀式转前缀式

可以通过将后缀式从右往左扫描,并将数值顺序压入栈中,遇到运算符时弹出两个数值,构造以运算符为根节点的表达式树,最后前序遍历表达式树即可得到前缀式。例如,后缀式表达式2 3 4 * + 5 -对应的前缀式为- + * 3 4 2 5。

那下面我们来看一道题吧:

给你一个逆波兰表达式(后缀式)式子的结果

仔细观察后缀表达式可以发现,其特点就是数字先保存下来,然后遇到符号就计算,例如”123+”,遇至+号就将2+3加起来变成5再继续其他操作,直到最后完成。

如果用栈来解释就是遇见数字即进栈,遇见运算符,则取出栈中最上面的两个元素进行计算,最后将运算结果入栈。实现代码其实很容易:

public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack();
        for(String token:tokens){
            //如果是数字则入栈
            if(!Character.isDigit(token.charAt(0))&&token.length()==1){
                int b = stack.pop();
                int a = stack.pop();
                switch(token){
                    case "+": stack.push(a+b);break;
                    case "-": stack.push(a-b);break;
                    case "*": stack.push(a*b);break;
                    case "/": stack.push(a/b);break;
                }
            }else{
                stack.push(Integer.parseInt(token));
            }
        }
        return stack.peek();

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值