1 栈的概述以及基本代码实现
栈(Stack)是一种先入后出的有序列表,先进入的元素总是存在于栈底。删除和加入新元素都是在栈顶进行操作的
简单的代码实现如下,主要就是一个固定大小的数组stack(这里假设是专门放整数的数组),一个栈顶指针top
class ArrayStack{
private int maxSize;
private int[] stack;
private int top = -1;
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//判别栈满
public boolean isFull() {
return top == maxSize - 1;
}
//判别栈空
public boolean isEmpty() {
return top == -1;
}
//元素入栈
public void push(int value) {
if(isFull()) {
System.out.println("满了");
return;
}
top++;
stack[top] = value;
}
//元素出栈 不用删除数据,后面新数据会覆盖,或者垃圾数据被JVM回收
public int pop() {
if(isEmpty()) {
throw new RuntimeException("空的");
}
return stack[top--];
}
//打印栈
public void list() {
if(isEmpty()) {
System.out.println("空");
return;
}
for(int i = top; i >= 0; i--) {
System.out.printf("stack[%d] = %d\n", i, stack[i]);
}
}
}
2 用栈实现综合计算器
2.1初步想法
向计算器中输入3-12*10+2,计算结果会直接出来。之所以要用到栈,是因为运算符之间有优先级之分
韩顺平的数据结构课程用栈实现计算器的思路如下:
直观点就是,先把乘除法这种优先级比较高的解决掉,剩下的只剩加减法了,直接就可计算了。因此设置一个符号栈s1,一个数字栈s2。发现数字直接入栈,发现符号后如果符号栈空直接入栈,如果符号栈不空,对比下这个符号X和符号栈最顶端的符号,如果当前符号优先级低于或者等于栈中的符号,就说明前面那个运算符可以“肆无忌惮”地进行运算了,那就先pop出符号栈的顶端运算符和数栈的两个数字进行运算,结果入数栈,先算减少一波数字和运算符。以此类推,直到碰到等于或者级别低于X的运算符,此时X入符号栈。当所有符号数字入栈完毕,依次做运算就可得出结果。
2.2 发现问题
这个方法在验证上面式子的时候结果是正确的,但是如果输入 3-2*6+2的时候就会得-9,原来是最后一步弹栈计算出了问题。
最后一步之前 数栈从顶至底依次是 2, 12, 3 ,符号栈从顶至底是+ -。一步步推下来,是减号哪里出了问题。解决思路有三个:
思路一是最后弹栈计算顺序修改:可以在用两个栈,依次接收符号栈和数字栈弹出来的东西再做运算,这样就改成了正序运算。
思路二是思考下为甚么会出现这种情况,-后买你跟着+就会出问题,按照上面的思路,连续的±是不会共存的,只可能是中间的乘除法隔绝
了两边的加减法。解决思路是用while循环判断,直到碰到比当前的符号X低的时候X才入符号栈,而上面的韩老师思路只是对比了一次。
思路三是遇见减号就把下一个数字取反,同时减号变加号入栈。这个从根本上消灭了减号这个运算。这个思路碰巧解决了另一个情况,比如 9 *-6,就不会在96之间入两个符号金栈。而是入9 -6金数字栈, ×进符号栈。笔者采用了思路三。
3 代码实现
首先在栈类中加入判断运算符和比较运算符优先级的方法,然后是运算方法(式子传过来的是运算符字符串,需要转化成运算)
public int priority(int oper) {
if(oper == '*' || oper == '/') {
return 1;
}else if(oper == '+' || oper == '-') {
return 0;
}else {
return -1;
}
}
//判断是不是运算符
public boolean isOper(char val) {
return val == '+' || val == '-' || val == '*' || val == '/';
}
public int cal(int num1, int num2, int oper) {
int res = 0;
switch(oper) {
case '+' :
res = num1 + num2;
break;
//由于笔者采用思路3,消灭了减号,因此下面的这个case其实没必要写
case '-':
res = num2 - num1;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num2 / num1;
break;
default:
break;
}
return res;
}
public class Calculator {
public static void main(String[] args) {
// TODO Auto-generated method stub
String expression = "3-12*10+2";
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
int index = 0;//用于扫描
int num1 = 0;
int num2 = 0;
int oper = 0; // 符号虽然是char,但是后面的priority方法中会自动转成int, 'a' == 97是true
int res = 0;
char ch = ' ';
boolean isNegative = false;//判断下一个数字是否需要变号
while(true) {
ch = expression.charAt(index);
//判断ch
if(operStack.isOper(ch)) {
//或者出现减号就转负数,前面变加号
if(ch == '-') {
isNegative = true;
ch = '+';
}
if(operStack.isEmpty()) {
//为空,直接入栈
operStack.push(ch);
}else {
if(operStack.priority(ch) <= operStack.priority(operStack.peek())) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
//当前符号入栈
operStack.push(ch);
}else {
//当前操作符优先级大于栈中的
operStack.push(ch);
}
}
}else {
//Integer.parseInt不好使,因为要求是string
//不能数字直接入栈,需要向后再看一下,防止多位数
String newCh = "" + ch;
while((index + 1) <= (expression.length() - 1) && !numStack.isOper(expression.charAt(index + 1))) {
newCh += expression.charAt(index + 1);
index++;
}
//呼应前面的负号问题
if(isNegative) {
numStack.push(-Integer.parseInt(newCh));
isNegative = false;
}else {
numStack.push(Integer.parseInt(newCh));
}
}
index++;
if(index >= expression.length()) {
break;
}
}
//扫描完毕 依次pop
while(true) {
if(operStack.isEmpty()) {
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
}
System.out.printf("表达式%s = %d", expression, numStack.pop());
}
}