第5章 栈
1、前言
先看一个例子,请输入一个表达式
计算式:[7 * 2 * 25+1-5+3-3] 点击计算【如下图】
请问: 计算机底层是如何运算得到结果的?
注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5, 但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题。—> 栈
2、栈的介绍和应用场景
2.1 栈的基本介绍
- 栈的英文为(stack)
- 栈是一个先入后出(FILO-First In Last Out)的有序列表。先进后出,后进先出。
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
5.出栈(pop)和入栈(push)的概念(如下图所示)
2.2 栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
- 二叉树的遍历。
- 图形的深度优先(depth一first)搜索法。
3、栈的思路分析及实现
- 用数组模拟栈的使用,由于栈是一种有序列表,当然可以使用数组的结构来储存栈的数据内容,下面我们就用数组模拟栈的出栈,入栈等操作。
- 实现思路分析,示意图如下:
实现栈的思路分析
- 使用数组来模拟栈
- 定义一个 top 来表示栈顶,初始化 为 -1
- 入栈的操作,当有数据加入到栈时, top++; stack[top] = data; (stack[++top] = data)
- 出栈的操作, int value = stack[top]; top-- ; return value; (return stack[top- - ])
3.1 数组模拟实现栈
public class ArrayStackDemo {
public static void main(String[] args) {
Cal stack = new Cal(4);
int key = 0;
boolean flag = true;
Scanner sc = new Scanner(System.in);
while(flag){
System.out.println("1 显示栈 2 退出");
System.out.println("3 进栈 4 出栈 ");
System.out.print("请选择:");
key = sc.nextInt();
switch(key){
case 1:
try {
stack.list();
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 2:
sc.close();
System.out.println("exit ....");
flag = false;
break;
case 3:
System.out.print("请输入一个数:");
int value = sc.nextInt();
stack.push(value);
break;
case 4:
try {
int pop = stack.pop();
System.out.println("出栈数据是 " + pop);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
break;
}
}
}
}
//定义一个ArrayStack表示栈
class ArrayStack{
private int maxSize;//栈的大小
private int[] stack;//数组模拟栈
private int top = -1;//top表示栈顶,初始化-1
public ArrayStack() {
}
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;
}
//入栈-push
public void push(int value){
if( isFull() ){
System.out.println("stack full..进栈失败");
return;
}
/*top++;
stack[top] = value;*/
stack[++top] = value;
System.out.println("success");
}
//出栈pop
public int pop(){
if(isEmpty()){
throw new RuntimeException("stack empty..");
}
/*int value = stack[top];
top--;
return value;*/
return stack[top--];
}
//打印
public void list(){
if(isEmpty()){
throw new RuntimeException("stack empty..");
}
//遍历时需要从栈顶显示数据
for (int i =top ; i > -1 ;i--) {
System.out.println(stack[i] +"\t");
}
}
}
3.2 链表模拟实现栈
课后扩展:用链表实现栈
//因为栈具有先进后出,后进先出的特性,为了方便操作 使用【头插法】 来更好的形容栈这一特性
public class LinkedStackDemo {
public static void main(String[] args) {
LinkedStack stack = new LinkedStack();
int key = 0;
boolean flag = true;
Scanner sc = new Scanner(System.in);
while(flag){
System.out.println("1 打印栈 2 长度");
System.out.println("3 进栈 4 出栈 ");
System.out.println("5 取栈头 6 退出");
System.out.print("请选择:");
key = sc.nextInt();
switch(key){
case 1:
stack.list();
break;
case 2:
int size = stack.size();
System.out.println("栈的大小为:" + size);
break;
case 3:
System.out.print("请输入一个数:");
int value = sc.nextInt();
stack.push(value);
break;
case 4:
try {
Object pop = stack.pop();
System.out.println("出栈数据是 " + pop);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 5:
try {
Object obj = stack.peek();
System.out.println("栈头 data = " +obj );
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 6:
sc.close();
System.out.println("exit ....");
flag = false;
default:
break;
}
}
}
}
class LinkedStack{
Node top = null;//头指针,指向链表第一个结点
public LinkedStack() {
top = new Node(null,null);
}
//判断栈空
public boolean isEmpty(){
return top.next == null;
}
//入栈 头插法
public void push(Object data){
Node n = new Node(data);
if(top.next == null){
top.next = n;
}else{
n.next = top.next;
top.next = n;
}
System.out.println("success");
}
//出栈 返回栈顶元素且删除
public Object pop(){
if(isEmpty()){
throw new RuntimeException("栈空 无法出栈");
}
Object data = null;
data = top.next.data;
top.next = top.next.next; //将top指向链表的第二个结点,达到模拟出栈效果
return data;
}
//返回栈顶元素
public Object peek(){
if(isEmpty()){
throw new RuntimeException("栈空 ");
}
return top.next.data;
}
//显示遍历
public void list(){
if(isEmpty()){
System.out.println("stack empty");
return;
}
Node cur = top;
while(cur.next != null){
System.out.println(cur.next.data);
cur = cur.next;
}
}
//求出栈的大小
public int size(){
Node cur = top;
int i = 0;
while(cur.next != null){
i++;
cur = cur.next;
}
return i;
}
}
class Node{
public Object data;
public Node next;
public Node() {
}
public Node(Object data) {
this.data = data;
}
public Node(Object data,Node next) {
this.data = data;
this.next = next;
}
}
4、栈实现计算器(中缀)
4.1 思路分析
使用栈完成计算 一个表达式的结果
使用栈完成表达式的计算思路
首先准备两个栈: 数栈(存储数字)、符号栈(存储符号)
通过一个 index 值(索引),来遍历我们的表达式
如果我们发现是一个数字, 就直接入数栈
如果发现扫描到是一个符号, 就分如下情况
3.1 如果发现当前的符号栈为 空,就直接入栈
3.2 如果符号栈有操作符,就进行比较,
- 如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果入数栈,然后将当前的操作符入符号栈,
- 如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行.
最后在数栈只有一个数字,就是表达式的结果
4.2 代码实现
public class Calculator {
public static void main(String[] args) {
String expression = "71-1+10*2";
//创建数字栈 和 符号栈
Cal numStack = new Cal(5);
Cal operStack = new Cal(5);
//定义相关变量
int index = 0;//扫描下标
int n1 = 0;//先出栈的数
int n2 = 0;//后出栈的数
int oper = 0;//操作符
int res = 0;//结果
char ch = ' ';//将每次扫描得到的char保存到ch
String keepNum= "";//用于拼接多位数
//开始while循环扫描expression
while (true) {
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())){
n1 = numStack.pop();
n2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(n1, n2, oper);
//把结果如数栈 且将当前符号入符号栈
numStack.push(res);
operStack.push(ch);
} else {
/*如果当前运算符的优先级大于栈顶的操作符就直接入符号栈*/
operStack.push(ch);
}
} else {
//空直接加入符号栈
operStack.push(ch);
}
} else {
//扫描是数,则指如数栈
//numStack.push( (ch - 48) ); //‘1’ = 49
//多位数处理
/* 分析思路如下:
1.当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
2.在处理数,需要向exception的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
3.因此我们需要定义一个变量 字符串,用于拼接*/
keepNum += ch;
//先判断是否是最后一位,是则直接添加
if (index == expression.length() - 1) {
numStack.push(Integer.parseInt(keepNum));
} else {
//判断下一位是不是数字,是继续扫描 不是则入数栈
if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
numStack.push(Integer.parseInt(keepNum));
//入栈之后必须至空,否则后面会继续拼接
keepNum = "";
}
}
}
//让index+1,且判断是否扫描到expression最后
index++;
if (index >= expression.length()) {
break;
}
}
//当表达式扫描完毕,就顺序的从数栈和符号栈pop出相应的数和符号,并运行
while( true ){
//如果符号栈为空,则说明计算完成,数栈中只有一位(结果)
if(!operStack.isEmpty()){
n1 = numStack.pop();
n2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(n1 , n2 , oper);
//把结果如数栈 且将当前符号入符号栈
numStack.push(res);
}else{
break;
}
}
System.out.println(expression +" = " + numStack.pop());
}
}
//定义一个ArrayStack表示栈
class Cal {
private int maxSize;//栈的大小
private int[] stack;//数组模拟栈
private int top = -1;//top表示栈顶,初始化-1
public Cal(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//栈满
public boolean isFull(){
return top == maxSize-1;
}
//栈空
public boolean isEmpty(){
return top == -1;
}
//入栈-push
public void push(int value){
if( isFull() ){
System.out.println("stack full..进栈失败");
return;
}
stack[++top] = value;
}
//出栈pop
public int pop(){
if(isEmpty()){
throw new RuntimeException("stack empty (pop)");
}
int value = stack[top];
top--;
return value;
}
//返回栈顶
public int peek(){
return stack[top];
}
//打印
public void list(){
if(isEmpty()){
System.out.println("stack empty (list)");
return;
}
//遍历时需要从栈顶显示数据
for (int i =top ; i > -1 ;i--) {
System.out.println(stack[i] +"\t");
}
}
//返回运算符的优先级,优先级是程序员来确定的,优先级使用数字来表示 只有+-*/
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 =='/';
}
/**
* 计算方法
* @param n1 后一个数
* @param n2 前一个数
* @param ope 操作符 + - * /
* @return
*/
public int cal( int n1 ,int n2 ,int ope){
int result = 0;
switch (ope){
case '+':
result = n2 + n1;
break;
case '-':
result = n2 - n1;//notice
break;
case '*':
result = n2 * n1;
break;
case '/':
result = n2 / n1;
break;
default:
break;
}
return result;
}
}
5、前缀 中缀 后缀表达式规则
5.1 前缀表达式(波兰表达式)
前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6
前缀表达式的计算机求值
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
- 从右至左扫描,将6、5、4、3压入堆栈
- 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
- 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
- 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
5.2 中缀表达式
中缀表达式就是常见的运算表达式,如(3+4)×5-6
中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)
后缀表达式
5.3 后缀表达式
后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
中举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
后缀表达式的计算机求值
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和
栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
6、逆波兰表达式分析与实现
完成一个逆波兰计算器,要求完成如下任务:
- 输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果
- 支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持对整数的计算。
- 思路分析
例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
- 从左至右扫描,将3和4压入堆栈;
- 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
- 将5入栈;
- 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
- 将6入栈;
- 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
import javax.swing.plaf.nimbus.State;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
String suffixExpression = "3 4 + 5 × 6 -";//中缀 (3+4)×5-6
//String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";//中缀 4 * 5 - 8 + 60 + 8/2
List<String> list = getListString(suffixExpression);
System.out.println(list);
int res = calculate(list);
System.out.println("result = " +res);
}
//将后缀(逆波兰)表达式进行分割保存早list集合中
public static List<String> getListString(String Expression){
String[] spilt = Expression.split(" ");
List<String> list = new ArrayList<>();
for (String s : spilt){
list.add(s);
}
return list;
}
/*
1.从左至右扫描,将3和4压入堆栈;
2.遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3.将5入栈;
4.接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5.将6入栈;
6.最后是-运算符,计算出35-6的值,即29,由此得出最终结果
*/
// 34+5*6-
//从左至右扫描 遇见数则入栈,符号出栈计算结果然后将结果入栈
public static int calculate(List<String> list){
Stack<String> stack = new Stack<>();
for (String item : list){
//正则取出数据
if(item.matches("\\d+")){//匹配的是多位数
stack.push(item);
}else{
//符号
int n2 = Integer.parseInt(stack.pop());
int n1 = Integer.parseInt( stack.pop());
int res = 0;
if(item.equals("+")){
res = n1 + n2;
}else if(item.equals("-")){
res = n1 - n2;
}
else if(item.equals("*")){
res = n1 * n2;
}
else if(item.equals("/")){
res = n1 / n2;
}
else {
throw new RuntimeException("运算符有误");
}
//把结果入栈
stack.push(String.valueOf(res));
}
}
//把最后结果返回
return Integer.parseInt(stack.pop());
}
}
7、中缀转后缀表达式思路分析及实现
大家看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将 中缀表达式转成后缀表达式。
7.1 思路分析
- 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
- 从左至右扫描中缀表达式;
- 遇到操作数时,将其压s2;
- 遇到运算符时,比较其与s1栈顶运算符的优先级:
(1).如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(2).否则,若当前操作符优先级比栈顶运算符的高,也将运算符压入s1;
(3).否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;- 遇到括号时: (1) 如果是左括号“(”,则直接压入s1 (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃.
- 重复步骤2至5,直到表达式的最右边
- 将s1中剩余的运算符依次弹出并压入s2
- 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式.
举例说明:
将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式"1 2 3 + 4 × + 5 –"的过程如下:
7.2 代码实现
import javax.swing.plaf.nimbus.State;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
/*String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
List<String> list = getListString(suffixExpression);
System.out.println(list);
int res = calculate(list);
System.out.println("result = " +res);*/
String expression = "1+((2+3)*4)-5";
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println("中缀表达式list集合"+infixExpressionList);
List<String> suffixExpressionList = parseSuffixExpressionList(infixExpressionList);
System.out.println("后缀表达式list集合"+suffixExpressionList);
int result = calculate(suffixExpressionList);
System.out.println("result = " +result);
}
//将中缀表达式转换成对应的list
public static List<String> toInfixExpressionList(String s){
List<String> list = new ArrayList<>();
int index = 0;
String str ="" ;
char ch;
do{
//如果是非数字,需要加入list
if( (ch = s.charAt(index)) < 48 || (ch = s.charAt(index) ) > 57){
list.add("" + ch);
index++;
}else{//数字 此时需要判断是否是多位数
while(index < s.length() && (ch = s.charAt(index)) >= 48 && (ch = s.charAt(index) ) <= 57){
str = "";
str += ch;//拼接
index++;
}
list.add(str);
}
}while (index < s.length());
return list;
}
//把得到的中缀list集合表达式转换为后缀表达式
public static List<String> parseSuffixExpressionList(List<String> list){
Stack<String> s1 = new Stack<>();//符号栈
//中间结果栈 此战后续还要反序操作,直接使用list
List<String> s2 =new ArrayList<>();
for (String item : list){
if(item.matches("\\d+")){
//数字直接入栈
s2.add(item);
}else if(item.equals("(")){
//(直接入栈
s1.push(item);
}else if(item.equals(")")){
//遇见)时从s1出栈直到遇到(为止
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();//将 ( 从s1弹出
}else{
//当item优先级小于s1栈顶运算符,将s1栈顶运算符弹出加入s2,在与s1
//运算符比较优先级
while( s1.size() != 0 &&
Operation.getValue(s1.peek()) >= Operation.getValue(item)){
s2.add(s1.pop());
}
//将item入栈
s1.push(item);
}
}
//将s1剩余运算符一次加入且弹出加入s2
while(s1.size() != 0){
s2.add(s1.pop());
}
return s2;
}
//将后缀(逆波兰)表达式进行分割保存早list集合中
public static List<String> getListString(String Expression){
String[] spilt = Expression.split(" ");
List<String> list = new ArrayList<>();
for (String s : spilt){
list.add(s);
}
return list;
}
/*
1.从左至右扫描,将3和4压入堆栈;
2.遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3.将5入栈;
4.接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5.将6入栈;
6.最后是-运算符,计算出35-6的值,即29,由此得出最终结果
*/
// 34+5*6-
//从左至右扫描 遇见数则入栈,符号出栈计算结果然后将结果入栈
public static int calculate(List<String> list){
Stack<String> stack = new Stack<>();
for (String item : list){
//正则取出数据
if(item.matches("\\d+")){//匹配的是多位数
stack.push(item);
}else{
//符号
int n2 = Integer.parseInt(stack.pop());
int n1 = Integer.parseInt( stack.pop());
int res = 0;
if(item.equals("+")){
res = n1 + n2;
}else if(item.equals("-")){
res = n1 - n2;
}
else if(item.equals("*")){
res = n1 * n2;
}
else if(item.equals("/")){
res = n1 / n2;
}
else {
throw new RuntimeException("运算符有误");
}
//把结果入栈
stack.push(String.valueOf(res));
}
}
//把最后结果返回
return Integer.parseInt(stack.pop());
}
}
//编写一个Operation,可以返回运算符对应的优先级
class Operation{
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 1;
private static int DIV = 1;
public static int getValue(String operation){
int res = 0;
switch (operation){
case "+":
res = ADD;
break;
case "-":
res = SUB;
break;
case "*":
res = MUL;
break;
case "/":
res = DIV;
break;
default:
System.out.println("not exist operation");
break;
}
return res;
}
}
7.3 完整版代码 实现
完整版的逆波兰计算器,功能包括
- 支持 + - * / ( )
- 多位数,支持小数,
- 兼容处理, 过滤任何空白字符,包括空格、制表符、换页符
注:逆波兰计算器完整版考虑的因素较多,下面给出完整版代码供参考学习,其基本思路和前面一样,也是使用到:中缀表达式转后缀表达式。
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>());
/**
*
* @Description 去除所有空白符
*/
public static String replaceAllBlank(String s) {
// \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]
return s.replaceAll("\\s+", "");
}
/**
*
* @Description 判断是不是数字 int double long float
*/
public static boolean isNumber(String s) {
Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
return pattern.matcher(s).matches();
}
/**
*
* @Description 判断是不是运算符
*/
public static boolean isSymbol(String s) {
return s.matches(SYMBOL);
}
/**
*
* @Description 匹配运算等级
*/
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;
}
/**
* 匹配
*/
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;
}
/**
* 算出结果
*/
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;
}
/**
*
* @Description 运算
*/
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();
}
}
}