第四章 栈实现综合计算器

文章目录

  • 第四章 栈实现综合计算器
  • 一、栈
    • 1.介绍
    • 2.应用场景
    • 3.思路
    • 4.代码实现
  • 二、综合计算器 v1.0
    • 1.思路
    • 2.代码实现
  • 三、前缀、中缀和后缀表达式规则
    • 1.前缀表达式(波兰式)
    • 2.中缀表达式
    • 3.后缀表达式(逆波兰表达式)
  • 四、综合计算器 v2.0
    • 1.要求
    • 2.思路
    • 3.代码实现
  • 五、中缀表达式转换为后缀表达式
    • 1.思路
    • 2.代码实现
  • 六、综合计算器 v3.0
    • 1.要求
    • 1.代码实现

一、栈

1.介绍

  • 栈的英文为 stack
  • 栈是一个先入后出(FILO - First In Last Out)的有序列表
  • 栈是限制线性表中元素的插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)
  • 根据栈的定义可知,最先放入栈中的元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

图解方式说明出栈(pop)和入栈(push)的概念

2.应用场景

  1. 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中。直到子程序执行完再将地址取出,以回到原来的程序中
  2. 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中
  3. 表达式的转换【中缀表达式转后缀表达式】与求值
  4. 二叉树的遍历
  5. 图形的深度优先(depth - first)搜索法

3.思路


数组模拟栈的思路分析

  1. 使用数组来模拟栈
  2. 定义一个 top 来表示栈顶,初始化为 -1
  3. 入栈的操作,当有数据加入栈是,top++;stack[top] = data
  4. 出栈的操作,int value = stack[top];top–,return value

4.代码实现

package com.sisyphus.stack;import java.util.Scanner;public class ArrayStackDemo {public static void main(String[] args) {//测试一下 ArrayStack 是否正确//先创建一个 ArrayStack 对象 -> 表示栈ArrayStack stack = new ArrayStack(4);String key = "";boolean loop = true;//控制是否退出菜单Scanner scanner = new Scanner(System.in);while(loop){System.out.println("show:表示显示栈");System.out.println("exit:退出程序");System.out.println("push:表示添加数据到栈(入栈)");System.out.println("pop:表示从栈取出数据(出栈)");System.out.println("请输入你的选择");key = scanner.next();switch(key){case "show":stack.list();break;case "push":System.out.println("请输入一个数");int value = scanner.nextInt();stack.push(value);break;case "pop":try {int res = stack.pop();System.out.printf("出栈的数据时 %d\n",res);} catch (Exception e) {System.out.println(e.getMessage());}break;case "exit":scanner.close();loop = false;break;}}System.out.println("程序退出~~~");}
}//定义一个 ArrayStack 表示栈
class ArrayStack{private int maxSize;//栈的大小private int[] stack;//数组,数组模栈,数据就放在该数组private int top = -1;//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;}//入栈 - pushpublic void push(int value){//先判断是否栈满if (isFull()){System.out.println("栈满");return;}top++;stack[top] = value;}//出栈 - pop,将栈顶的数据返回public int pop(){//先判断栈是否空if (isEmpty()){//抛出异常throw new RuntimeException("栈空,没有数据~");}int value = stack[top];top--;return value;}//显示栈的情况【遍历栈】,遍历时,需要从栈顶开始显示数据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]);}}}

二、综合计算器 v1.0

1.思路

创建两个栈一个数栈 numStack 存放数,另一个符号栈 operStack 存放运算符

  1. 通过一个 index 值(索引),来遍历我们的表达式
  2. 如果是一个数,就压入数栈
  3. 如果是一个符号,就分为如下情况
    1. 如果发现当前的符号栈为空,就直接入栈
    2. 如果符号栈中有操作符,进行比较,如果当前的操作符优先级小于或等于栈顶的操作符,就需要从数栈中 pop 出两个数,再从符号栈中 pop 出一个符号,进行运算,将得到的结果压入数栈,最后将当前的操作符压入符号栈;如果当前的操作符优先级大于栈顶的操作符就直接压入栈顶
  4. 当表达式扫描完毕,就顺序地从数栈和符号栈中 pop 出相应的数和符号进行运算
  5. 最后在数栈中只有一个数字,就是表达式的结果

例子:3 + 2 * 6 - 2

数栈 符号栈
null null
3 +
3 2 + ×
3 2 6 + × (-)
3 12 + -
3 12 2 + -
3 10 +
13 null

2.代码实现

扩展数组栈的代码

  • 判断一个元素是是数字还是符号
  • 判断操作符优先级
  • 运算
package com.sisyphus.stack;import java.util.Scanner;public class Calculator {public static void main(String[] args) {//根据思路,完成表达式运算String expression = "270+20*6-4";ArrayStack2 numStack = new ArrayStack2(10);ArrayStack2 operStack = new ArrayStack2(10);//定义需要的相关变量int index = 0;//用于扫描int num1 = 0;int num2 = 0;int oper = 0;int res = 0;char ch = ' ';  //将每次扫描得到的 char 保存到 chString keepNum = "";//用于拼接多位数//开始 while 循环,扫描 expressionwhile (true) {//依次得到 expression 的每一个字符//str=str.substring(int beginIndex,int endIndex);截取str中从beginIndex开始至endIndex结束时的字符串,并将其赋值给str;//char charAt(int index);获取当前字符串指定下标对应的字符ch = expression.substring(index, index + 1).charAt(0);//判断 ch 是什么元素,然后做相应的处理if (operStack.isOper(ch)) {//如果是运算符//判断当前的符号栈是否为空if (!operStack.isEmpty()) {//如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符//就需要从数栈中 pop 出两个数,再从符号栈中 pop 出一个符号,进行运算,将得到的结果压入数栈,最后将当前操作符压入符号栈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 {//如果为空直接压入符号栈...operStack.push(ch);}}else{//在处理数时,需要向 expression 的表达式的 index 后再看一位,如果是数就拼接,如果是运算符就入栈//因此我们需要定义一个变量字符串,用于拼接keepNum += ch;//如果 ch 已经是 expression 的最后一位,就直接入栈if (index == expression.length() - 1){numStack.push(Integer.parseInt(keepNum));}else{//判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符则入栈if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))){//如果后一位是运算符,则入栈(keepNum是字符串需要转换为 int)numStack.push(Integer.parseInt(keepNum));//清空 keepNumkeepNum = "";}}}//让index + 1,并判断是否扫描到 expression 最后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);//结果入栈}//将数栈最后的数 pop,就是结果int res2 = numStack.pop();System.out.printf("表达式%s = %d",expression,res2);}
}//先创建一个栈,直接使用前面创建好的
//定义一个 ArrayStack2 表示栈,需要扩展功能
class ArrayStack2{private int maxSize;//栈的大小private int[] stack;//数组,数组模栈,数据就放在该数组private int top = -1;//top 表示栈顶,初始化为 -1//构造器public ArrayStack2(int maxSize) {this.maxSize = maxSize;stack = new int[this.maxSize];//初始化数组}//增加一个方法,可以返回当前栈顶的值,但不是真正的 poppublic int peek(){return stack[top];}//栈满public boolean isFull(){return top == maxSize - 1;}//栈空public boolean isEmpty(){return top == -1;}//入栈 - pushpublic void push(int value){//先判断是否栈满if (isFull()){System.out.println("栈满");return;}top++;stack[top] = value;}//出栈 - pop,将栈顶的数据返回public int pop(){//先判断栈是否空if (isEmpty()){//抛出异常throw new RuntimeException("栈空,没有数据~");}int value = stack[top];top--;return value;}//显示栈的情况【遍历栈】,遍历时,需要从栈顶开始显示数据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]);}}//返回运算符的优先级,优先级由程序员决定,使用数字表示//数字越大,优先级越高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;    //res 用于存放计算的结果switch(oper){case '+':res = num1 + num2;break;case '-':res = num2 - num1;  //注意顺序break;case '*':res = num1 * num2;break;case '/':res = num2 / num1;break;default:break;}return res;}
}

三、前缀、中缀和后缀表达式规则

1.前缀表达式(波兰式)

前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前

前缀表达式的计算机求值

从右向左扫描表达式,遇到数字时,将数字压入栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入入栈
重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

例如:(3 + 4) × 5 - 6 对应的前缀表达式就是 - × + 3 4 5 6
针对前缀表达式求值,步骤如下:

  1. 从右向左扫描,将 6、5、4、3 压入栈
  2. 遇到 + 运算符,pop 出 3 和 4(3 为栈顶元素,4 为次顶元素),计算出 3 + 4 = 7,将 7 压入栈
  3. 接下来是 × 运算符,pop 出 7 和 5 ,计算出 7 × 5 = 35 ,将 35 压入栈
  4. 最后是 - 运算符,计算出 35 - 6 = 29,将 29 压入栈,29就是最终结果

2.中缀表达式

中缀表达式就是常见的运算表达式,如(3 + 4) × 5 - 6
中缀表达式得=的求值是我们人类最熟悉的,但是对计算机来说却不好操作。因此,在计算表达式的时候,往往会将中缀表达式转换成其它表达式来计算(一般转成后缀表达式)

3.后缀表达式(逆波兰表达式)

后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后

正常的表达式 逆波兰表达式
a + b a b +
a + ( b - c ) a b c - +
a + ( b - c ) × d a b c - d × +
a + d * ( b - c ) a d b c - × +
a = 1 + 3 a 1 3 + =

后缀表达式的计算机求值

从左向右扫描表达式,遇到数字时,将数字压入栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和栈顶元素),并将结果入栈
重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

例如:(3 + 4) × 5 - 6 对应的后缀表达式就是 3 4 + 5 × 6 -
针对后缀表达式求值,步骤如下:

  1. 从左向右扫描,将 3 和 4 压入栈
  2. 遇到 + 运算符,pop 出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3 + 4 = 7,将 7 压入栈
  3. 将 5 压入栈
  4. 接下来是 × 运算符,弹出 5 和 7,计算出 7 × 5 = 35,将 35 入栈
  5. 将 6 压入栈
  6. 最后是 - 运算符,计算出 35 - 6 = 29,将 29 压入栈,29 就是最终结果

四、综合计算器 v2.0

1.要求

  1. 输入一个逆波兰表达式(后缀表达式),使用栈(Stack<>),计算其结果
  2. 支持小括号和多位数整数

2.思路

见上文后缀表达式的计算机求值

3.代码实现

package com.sisyphus.stack;import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public class PolandNotation {public static void main(String[] args) {//先定义一个逆波兰表达式//( 30 + 4 ) × 5 - 6   =>   30 4 + 5 × 6 -   =>   164//4 * 5 - 8 + 60 + 8 / 2   =>   3 5 * 8 - 60 + 8 2 / +   =>   76//为了方便,逆波兰表达式的数字和符号之间使用空格隔开String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";//思路//1.先将" 3 4 + 5 × 6 - "   =>   放到 ArrayList 中//2.将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈完成计算List<String> list = getListString(suffixExpression);System.out.println("rpntList" + list);int res = calculate(list);System.out.println("计算的结果是" + res);}//拿到一个逆波兰表达式,依次将数据和运算符放入到 ArrayList 中public static List<String> getListString(String suffixExpression){//将 suffixExpression 分割String[] split = suffixExpression.split(" ");List<String> list = new ArrayList<String>();for(String ele: split){list.add(ele);}return list;}//完成对逆波兰表达式的运算public static int calculate(List<String> ls){//创建栈,只需要一个栈即可Stack<String> stack = new Stack<String>();//遍历 lsfor (String item: ls){//这里使用正则表达式来取出数if(item.matches("\\d+")){//匹配的是多位数//入栈stack.push(item);}else{//pop 出两个数,并运算,再入栈int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = 0;if (item.equals("+")){res = num1 + num2;}else if(item.equals("-")){res = num1 - num2;}else if (item.equals("*")){res = num1 * num2;}else if (item.equals("/")){res = num1 / num2;}else{throw new RuntimeException("运算符有误");}//把 res 入栈stack.push("" + res);//把整数转为字符串}}//最后留在 stack 中的数据是运算结果return Integer.parseInt(stack.pop());}
}

五、中缀表达式转换为后缀表达式

后缀表达式适合计算机进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此再开发中,我们需要将中缀表达式转成后缀表达式

1.思路

  1. 初始化两个栈:运算符栈 s1 和储存中间结果的栈 s2
  2. 从左向右扫描中缀表达式
  3. 遇到操作数时,将其压入 s2
  4. 遇到运算符时,比较其与 s1 栈顶运算符的优先级:
    1. 如果 s1 为空,或栈顶运算符为左括号 ” ( “ ,则直接将此运算符入栈
    2. 否则,若优先级比栈顶运算符高,也将运算符压入 s1
    3. 否则,将 s1 栈顶的运算符 pop 并压入到 s2 中,再次转到步骤 4.1 与 s1 中新的栈顶运算符进行比较
  5. 遇到括号时,
    1. 如果是左括号 ” ( " ,则直接压入 s1
    2. 如果是右括号 “ ) " ,则依次 pop s1 栈顶的运算符并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃
  6. 重复步骤 2 至 5 ,直到表达式扫描完毕
  7. 将 s1 中剩余的运算符依次 pop 并 压入 s2
  8. 依次 pop s2 中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

例如:1 + ( ( 2 + 3 ) × 4 ) - 5 => -5+×4+321
将 s2 出栈:-5+×4+321 => 123+4×+5-

扫描到的元素 s2(栈底->栈顶) s1(栈底->栈顶) 说明
1 1 数字,直接入栈
+ 1 + s1 为空,运算符直接入栈
( 1 + ( 左括号,直接入栈
( 1 + ( ( 同上
2 1 2 + ( ( 数字
+ 1 2 + ( ( + s1 栈顶为左括号,运算符直接入栈
3 1 2 3 + ( ( + 数字
) 1 2 3 + + ( 右括号,弹出运算符直至遇到左括号
× 1 2 3 + + ( × s1 栈顶为左括号,运算符直接入栈
4 1 2 3 + 4 + ( × 数字
) 1 2 3 + 4 × + 右括号,弹出运算符直至遇到左括号
- 1 2 3 + 4 × + - - 与 + 优先级相同,因此弹出 +,再压入 -
5 1 2 3 + 4 × + 5 - 数字
结束 1 2 3 + 4 × + 5 - s1 中剩余的运算符

2.代码实现

package com.sisyphus.stack;import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public class PolandNotation {public static void main(String[] args) {//完成将一个中缀表达式转成后缀表达式的功能//说明//1、 1 + ( ( 2 + 3 ) × 4 ) - 5   =>   123+4×+5-//2、 因为直接对一个字符串进行操作不方便,因此先将 “1 + ( ( 2 + 3 ) × 4 ) - 5” 转成中缀表达式对应的 List//    即 “1 + ( ( 2 + 3 ) × 4 ) - 5”   =>   ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]//3、 将得到的中缀表达式对应的 List 转成后缀表达式对应的 List//    即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]   =>   ArrayList [1,2,+,4,*,+,5,-]String expression = "1+((2+3)*4)-5";    //注意表达式 *List<String> infixExpressionList = toInfixExpressionList(expression);System.out.println("中缀表达式对应的List=" + infixExpressionList);    //ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]List<String> suffixExpressionList = parseSuffixExpressionList(infixExpressionList);System.out.println("后缀表达式对应的List=" + suffixExpressionList);System.out.printf("expression=%d",calculate(suffixExpressionList));//        //先定义一个逆波兰表达式
//        //( 30 + 4 ) × 5 - 6   =>   30 4 + 5 × 6 -   =>   164
//        //4 * 5 - 8 + 60 + 8 / 2   =>   3 5 * 8 - 60 + 8 2 / +   =>   76
//        //为了方便,逆波兰表达式的数字和符号之间使用空格隔开
//        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
//        //思路
//        //1.先将" 3 4 + 5 × 6 - "   =>   放到 ArrayList 中
//        //2.将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈完成计算
//
//        List<String> list = getListString(suffixExpression);
//        System.out.println("rpntList" + list);
//        int res = calculate(list);
//        System.out.println("计算的结果是" + res);}//3、 将得到的中缀表达式对应的 List 转成后缀表达式对应的 List//    即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]   =>   ArrayList [1,2,+,4,*,+,5,-]public static List<String> parseSuffixExpressionList(List<String> ls){//定义两个栈Stack<String> s1 = new Stack<String>(); //符号栈//说明:因为 s2 这个栈,在整个转换过程中,没有 pop 操作,而且后面我们还需要逆序输出//因此比较麻烦,这里我们就不用 Stack<String>,直接使用 List<String> s2//Stack<String> s2 = new Stack<String>();List<String> s2 = new ArrayList<String>();   //储存中间结果的 List s2//遍历 lsfor(String item : ls){//如果是一个数,加入到 s2if (item.matches("\\d+")){s2.add(item);}else if(item.equals("(")){s1.push(item);}else if(item.equals(")")){//如果是右括号 “ ) " ,则依次 pop s1 栈顶的运算符并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃while(!s1.peek().equals("(")){s2.add(s1.pop());}s1.pop();//将左括号 pop 出 s1,消除小括号}else{//当 item 的优先级小于等于栈顶运算符的优先级,将 s1 栈顶的运算符 pop 并加入到 s2 中,再次转到步骤 4.1 与 s1 中新的栈顶运算符进行比较//问题:我们缺少一个比较优先级高低的方法while(s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){s2.add(s1.pop());}//还需要将 item 压入栈s1.push(item);}}//将 s1 中剩余的运算符依次 pop 并加入 s2while(s1.size() != 0){s2.add(s1.pop());}return s2;  //注意因为是存放到 List,因此按顺序输出就是对应的后缀表达式对应的 List}//方法:将中缀表达式转成对应的 List// s = "1+((2+3)×4)-5"public static List<String> toInfixExpressionList(String s){//定义一个 List,存放中缀表达式对应的内容List<String> ls = new ArrayList<String>();int i = 0;//这是一个指针,用于遍历中缀表达式字符串String str;//用于多位数的拼接char c;//每遍历到一个字符,就放入到 cdo{//如果 c 是一个非数字,就需要加入到 lsif((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){// '0'[48]  '9'[57]ls.add("" + c);i++;//i 需要后移}else{  //如果是一个数,需要考虑多位数str = "";//先将 str 置空while(i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){str += c;//拼接i++;}ls.add(str);}}while(i < s.length());return ls;}//拿到一个逆波兰表达式,依次将数据和运算符放入到 ArrayList 中public static List<String> getListString(String suffixExpression){//将 suffixExpression 分割String[] split = suffixExpression.split(" ");List<String> list = new ArrayList<String>();for(String ele: split){list.add(ele);}return list;}//完成对逆波兰表达式的运算public static int calculate(List<String> ls){//创建栈,只需要一个栈即可Stack<String> stack = new Stack<String>();//遍历 lsfor (String item: ls){//这里使用正则表达式来取出数if(item.matches("\\d+")){//匹配的是多位数//入栈stack.push(item);}else{//pop 出两个数,并运算,再入栈int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = 0;if (item.equals("+")){res = num1 + num2;}else if(item.equals("-")){res = num1 - num2;}else if (item.equals("*")){res = num1 * num2;}else if (item.equals("/")){res = num1 / num2;}else{throw new RuntimeException("运算符有误");}//把 res 入栈stack.push("" + res);//把整数转为字符串}}//最后留在 stack 中的数据是运算结果return Integer.parseInt(stack.pop());}
}//编写一个类 Operation 可以返回一个运算符对应的优先级
class Operation{private static int ADD = 1;private static int SUB = 1;private static int MUL = 2;private static int DIV = 2;//写一个方法,返回对应的优先级数字public static int getValue(String operation){int result = 0;switch(operation){case "+":result = ADD;break;case "-":result = SUB;break;case "*":result = MUL;break;case "/":result = DIV;break;default:System.out.println("不存在该运算符");break;}return result;}
}

六、综合计算器 v3.0

1.要求

  1. 支持 + - × / ()
  2. 多位数,支持小数
  3. 兼容处理,过滤任何空白字符,包括空格、制表符、换页符

1.代码实现

package com.sisyphus.reversepolishcal;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;public class ReversePolishMultiCalc {/*** 匹配 + - * / ( ) 运算符*/static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";static final String LEFT = "(";static final String RIGHT = ")";static final String ADD = "+";static final String MINUS= "-";static final String TIMES = "*";static final String DIVISION = "/";/*** 加減 + -*/static final int LEVEL_01 = 1;/*** 乘除 * /*/static final int LEVEL_02 = 2;/*** 括号*/static final int LEVEL_HIGH = Integer.MAX_VALUE;static Stack<String> stack = new Stack<>();static List<String> data = Collections.synchronizedList(new ArrayList<String>());/*** 去除所有空白符* @param s* @return*/public static String replaceAllBlank(String s ){// \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]return s.replaceAll("\\s+","");}/*** 判断是不是数字 int double long float* @param s* @return*/public static boolean isNumber(String s){Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");return pattern.matcher(s).matches();}/*** 判断是不是运算符* @param s* @return*/public static boolean isSymbol(String s){return s.matches(SYMBOL);}/*** 匹配运算等级* @param s* @return*/public static int calcLevel(String s){if("+".equals(s) || "-".equals(s)){return LEVEL_01;} else if("*".equals(s) || "/".equals(s)){return LEVEL_02;}return LEVEL_HIGH;}/*** 匹配* @param s* @throws Exception*/public static List<String> doMatch (String s) throws Exception{if(s == null || "".equals(s.trim())) throw new RuntimeException("data is empty");if(!isNumber(s.charAt(0)+"")) throw new RuntimeException("data illeagle,start not with a number");s = replaceAllBlank(s);String each;int start = 0;for (int i = 0; i < s.length(); i++) {if(isSymbol(s.charAt(i)+"")){each = s.charAt(i)+"";//栈为空,(操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及是 ) 不能直接入栈if(stack.isEmpty() || LEFT.equals(each)|| ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)){stack.push(each);}else if( !stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())){//栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek()) ){if(calcLevel(stack.peek()) == LEVEL_HIGH){break;}data.add(stack.pop());}stack.push(each);}else if(RIGHT.equals(each)){// ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())){if(LEVEL_HIGH == calcLevel(stack.peek())){stack.pop();break;}data.add(stack.pop());}}start = i ;    //前一个运算符的位置}else if( i == s.length()-1 || isSymbol(s.charAt(i+1)+"") ){each = start == 0 ? s.substring(start,i+1) : s.substring(start+1,i+1);if(isNumber(each)) {data.add(each);continue;}throw new RuntimeException("data not match number");}}//如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个stack 添加到队列Collections.reverse(stack);data.addAll(new ArrayList<>(stack));System.out.println(data);return data;}/*** 算出结果* @param list* @return*/public static Double doCalc(List<String> list){Double d = 0d;if(list == null || list.isEmpty()){return null;}if (list.size() == 1){System.out.println(list);d = Double.valueOf(list.get(0));return d;}ArrayList<String> list1 = new ArrayList<>();for (int i = 0; i < list.size(); i++) {list1.add(list.get(i));if(isSymbol(list.get(i))){Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));list1.remove(i);list1.remove(i-1);list1.set(i-2,d1+"");list1.addAll(list.subList(i+1,list.size()));break;}}doCalc(list1);return d;}/*** 运算* @param s1* @param s2* @param symbol* @return*/public static Double doTheMath(String s1,String s2,String symbol){Double result ;switch (symbol){case ADD : result = Double.valueOf(s1) + Double.valueOf(s2); break;case MINUS : result = Double.valueOf(s1) - Double.valueOf(s2); break;case TIMES : result = Double.valueOf(s1) * Double.valueOf(s2); break;case DIVISION : result = Double.valueOf(s1) / Double.valueOf(s2); break;default : result = null;}return result;}public static void main(String[] args) {//String math = "9+(3-1)*3+10/2";String math = "12.8 + (2 - 3.55)*4+10/5.0";try {doCalc(doMatch(math));} catch (Exception e) {e.printStackTrace();}}}

【Java数据结构与算法】第四章 栈实现综合计算器相关推荐

  1. Java数据结构和算法(四)——栈

    前面我们讲解了数组,数组更多的是用来进行数据的存储,纯粹用来存储数据的数据结构,我们期望的是插入.删除和查找性能都比较好.对于无序数组,插入快,但是删除和查找都很慢,为了解决这些问题,后面我们会讲解比 ...

  2. JAVA数据结构和算法:第一章(时间复杂度和空间复杂度)

    数据结构 数据结构基础概念 不论是哪所大学,数据结构和算法这门课都被贯上无趣.犯困.困难的标签,我们从最基础最通俗的语言去说起,保证通俗易懂. 数据结构到底是什么呢?我们先来谈谈什么叫数据. 数据:数 ...

  3. 【Java数据结构与算法】第九章 顺序查找、二分查找、插值查找和斐波那契查找

    第九章 顺序查找.二分查找.插值查找和斐波那契查找 文章目录 第九章 顺序查找.二分查找.插值查找和斐波那契查找 一.顺序查找 1.基本介绍 2.代码实现 二.二分查找 1.基本介绍 2.代码实现 三 ...

  4. 【Java数据结构与算法】第一章 稀疏数组和队列

    第一章 稀疏数组和队列 文章目录 第一章 稀疏数组和队列 一.线性结构和非线性结构 1.线性结构 2.非线性结构 二.稀疏数组 三.队列 1.队列 2.环形队列 一.线性结构和非线性结构 1.线性结构 ...

  5. 【Java数据结构与算法】第二章 单链表及简单面试题

    第二章 单链表 文章目录 第二章 单链表 一.单链表 1.基本介绍 2.思路 3.代码实现 二.简单面试题 1.求单链表中有效节点的个数 2.查找单链表中的倒数第k个节点(新浪面试题) 3.单链表的反 ...

  6. Java数据结构与算法(第二章数组)

    2019独角兽企业重金招聘Python工程师标准>>> 数组是应用最广泛的数据存储结构.它被植入到大部分编程语言中. Java中数组的基础知识     创建数组 在Java中把它们当 ...

  7. Java数据结构和算法(四)--链表

    日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...

  8. 【Java数据结构及算法实战】系列002:算法的四种描述方式

    本节是<Java数据结构及算法实战>系列的第2节,主要介绍描述算法的常用的4种方式. 要定义一个算法,我们可以用自然语言.流程图.伪代码的方式描述解决某个问题的过程或是编写一段程序来实现这 ...

  9. Java数据结构与算法(二)

    Java数据结构与算法(二) 第六章 递归 1 递归应用场景 2 递归的概念 3 递归调用机制 4 递归能解决什么样的问题 5 递归需要遵守的重要规则 6 递归-迷宫问题 6.1 迷宫问题 6.2 代 ...

最新文章

  1. [转]Linux awk 命令 说明
  2. js获取当前日期方法
  3. java for循环的这种写法怎么理解:for (; ; ) {},
  4. 5天学会python_学会Python自动制作Word,将看到一个5天4位数的赚钱机会
  5. testng 检查异常_TestNG异常– ExpectedExceptions,ExpectedExceptionsMessageRegExp
  6. tnt_esri.dat Arcgis8.1安装license
  7. SQLserver nText和varchar 不兼容
  8. 【连载】【STM32神舟III号实验例程】SysTick实验(11)
  9. macOS Big Sur 11.6.5 (20G527) Boot ISO 原版可引导镜像
  10. 哈夫曼树实现:统计文本信息,构造哈夫曼树,并对其进行编码与解码
  11. 计算机毕业设计之java+ssm乐轩公司订餐系统
  12. GIS影像数据格式说明
  13. 从2018年全球半导体数据中看物联网芯片产业现状
  14. Java数据结构与算法——线性查找 二分查找 插值查找
  15. 中国布洛芬产能全球第一,为何我们还是买不到?
  16. 炒股的智慧:股票投资93条秘籍
  17. linux下tar解压文件(解决unzip无法解压超过2gb文件方法
  18. 滚动代码Marquee详解(html滚动显示文字)
  19. 期权专题4:垂直价差期权
  20. word html签名,如何在Word中加入手写签名签名?

热门文章

  1. php提供了什么来实现反射,php提供实现反射的方法和实例代码
  2. java dao层的泛型get方法_dao层的泛型实现(2种方法)
  3. XML指南——XML元素
  4. Intellij插件之JRebel
  5. Oracle数据库学习(四)
  6. 阿里云终端连接与实例管理
  7. UNIX网络编程读书笔记:套接口选项
  8. Pairwork2 总结
  9. VS2008建立webserver(转)
  10. 四剑客查找字符_linux 四剑客 find 、grep、sed、awk整理