栈的一个实际需求

请输入一个表达式
计算式:[722-5+1-5+3-3] 点击计算【如下图】

请问: 计算机底层是如何运算得到结果的? 注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5, 但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题-> 栈。

栈的介绍

  1. 栈的英文为(stack) 栈是一个先入后出(FILO-First In Last Out)的有序列表。
  2. 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。
  3. 允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
  4. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
  5. 出栈(pop)和入栈(push)的概念(如图所示)

栈的应用场景

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

用数组模拟栈

思路:

代码:

import java.util.Scanner;public class ArrayStackDemo {public static void main(String[] args) {//测试//先创建一个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 {System.out.println("出栈的数据为:"+stack.pop());} 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;//表示栈顶//构造函数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;}//出栈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]);}}
}

编写一个计算器(支持正负、加减乘除运算,不支持括号)

public class Calculator {public static void main(String[] args) {// 测试String expression = "-10-6/3*2+5*2/2";//中缀表达式// 创建数栈和符号栈ArrayStack2 numStack = new ArrayStack2(10);ArrayStack2 operStack = new ArrayStack2(10);// 定义扫描int index = 0;int flag = 0;// 判断是否为负数int num1 = 0;int num2 = 0;int oper = 0;int res = 0;char ch = ' ';// 将每次扫描得到的char保存如chString keepNum = "";// 用于拼接多位数// 开始扫描while (true) {// 依次得到每一个字符ch = expression.charAt(index);// 判断ch是什么,然后再做相应处理if (operStack.isOper(ch)) {// 如果是运算符// 判断是否为-号if (ch == '-') {// 判断是否在算式首位if (index == 0) {flag = 1;index++;continue;} else {flag = 1;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 {// 如果是数,则直接入数栈// 如果当前操作符优先级大于栈顶的操作符,则直接如栈// numStack.push(ch - '0');不能发现一个数就立即入栈,因为可能是多位数// 故需要再往后看一位,如果是数则继续扫描,如果是符号则可以如栈// 因此需要定义一个字符串变量用于拼接keepNum += ch;// 如果ch已经是expression的最后一位,就直接入栈if (index == expression.length() - 1) {if (flag == 1) {numStack.push(-Integer.parseInt(keepNum));} else {numStack.push(Integer.parseInt(keepNum));}} else {// 判断下一个是不是数字,如果是数字,则继续扫描;如果是运算符,则入栈// 不能用index++if (operStack.isOper(expression.charAt(index + 1))) {// 入栈if (flag == 1) {numStack.push(-Integer.parseInt(keepNum));} else {numStack.push(Integer.parseInt(keepNum));}// 注意:一定要清空字符串keepNum = "";flag = 0;}}}// 让index加一,判断是否扫描到最后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出来System.out.printf("表达式%s = %d", expression, numStack.pop());}
}// 定义一个ArrayStack2表示栈
class ArrayStack2 {private int maxSize;// 栈的大小private int[] stack;// 用数组模拟栈private int 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;}// 入栈public void push(int value) {// 先判断是否满if (isFull()) {System.out.println("栈满");return;}top++;stack[top] = value;}// 出栈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]);}}// 返回运算符优先级,用数字表示// 数字越大,优先级越高public int priority(int i) {if (i == '*' || i == '/') {return 1;} else if (i == '+' || i == '-') {return 0;} else {return -1;}}// 判断是不是一个运算符public boolean isOper(int 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;case '-':res = num2 - num1;// 注意顺序break;case '*':res = num1 * num2;break;case '/':res = num2 / num1;break;default:break;}return res;}
}

前缀、中缀、后缀表达式

前缀表达式(波兰表达式):

  • 缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
  • 举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6

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

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

  1. 从右至左扫描,将6、5、4、3压入堆栈;
  2. 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈;
  3. 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈;
  4. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果。

中缀表达式:

  • 中缀表达式就是常见的运算表达式,如(3+4)×5-6
  • 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)
  • 上面的例子就是用中缀表达式来做的一个计算器

后缀表达式(逆波兰表达式):

  • 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后。
  • 举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
  • 再比如:

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

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

  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,由此得出最终结果。

下面我们完成一个逆波兰计算器:(已经将算式转换为了逆波兰表达式)

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 -//为了方便,数字和符号间用空格隔开String suffixExpression = "30 4 + 5 * 6 -";//思路://1.先将3 4 + 5 * 6 -放到ArrayList中//2.将ArrayList传递给一个方法,遍历ArrayList配合栈完成计算List<String> list = getListString(suffixExpression);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 str:split) {list.add(str);}return list;}//完成对逆波兰表达式的计算public static int calculate(List<String> list) {//创建一个栈Stack<String> stack = new Stack<String>();//遍历for(String item:list) {//这里使用正则表达式取出数if(item.matches("\\d+")) {//匹配的是多位数//入栈stack.push(item);}else {//pop出两个数并计算,再入栈int num2=Integer.parseInt(stack.pop());int num1=Integer.parseInt(stack.pop());int res=0;switch(item) {case "+":res=num1+num2;break;case "-":res=num1-num2;break;case "*":res=num1*num2;break;case "/":res=num1/num2;break;default:throw new RuntimeException("运算符有误");}//把res入栈stack.push(""+res);//把整数转换为字符串}}//最终留在stack中的数据即为运算结果return Integer.parseInt(stack.pop());}
}

如何将中缀表达式转换为逆波兰表达式并计算

具体步骤如下:

  1. 初始化两个栈:运算符栈s1和储存中间结果的栈s2;

  2. 从左至右扫描中缀表达式;

  3. 遇到操作数时,将其压s2;

  4. 遇到运算符时,比较其与s1栈顶运算符的优先级:
    (1) 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    (2) 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    (3) 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;

  5. 遇到括号时:
    (1) 如果是左括号“(”,则直接压入s1;
    (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;

  6. 重复步骤2至5,直到表达式的最右边;

  7. 将s1中剩余的运算符依次弹出并压入s2;

  8. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。

分析图解:

代码:

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 => 1 2 3 + 4 * + 5 –// 2. 因为对字符串操作不方便,因此可以先将1+((2+3)*4)-5的中缀表达式转为ArrayList// 3.将中缀表达式对应的List转为后缀表达式对应的ListString expression = "1+((2+3)*4)-5";System.out.println("原算式为:"+expression);List<String> infixExpression = toInfixExpressionList(expression);System.out.println("中缀表达式" + infixExpression);List<String> suffixExpression = parseSuffixExpressionList(infixExpression);System.out.println("后缀表达式" + suffixExpression);System.out.println("最终结果为:" + calculate(suffixExpression));}// 将中缀表达式转换为Listpublic static List<String> toInfixExpressionList(String s) {// 定义一个List存放中缀表达式内容List<String> list = new ArrayList<String>();int index = 0;// 用来扫描字符串String str;// 用于拼接多位数char c;// 每遍历一个字符存入c中do {// 如果是一个非数字,则加入list中if ((c = s.charAt(index)) < '0' || (c = s.charAt(index)) > '9') {list.add("" + c);// 将字符转换为字符串index++;// 后移} else {// 如果是一个数,则要考虑多位数str = "";// 先将str清空while (index < s.length() && (c = s.charAt(index)) >= '0' && (c = s.charAt(index)) <= '9') {str += c;// 进行拼接index++;}list.add(str);}} while (index < s.length());return list;}//将List转换为逆波兰表达式// [1,+,(,(,2,+,3,),*,4,),-,5] -> [1,2,3,+,4,*,+,5,–]public static List<String> parseSuffixExpressionList(List<String> list) {// 定义两个栈Stack<String> s1 = new Stack<String>();// 说明:因为s2栈在整个过程没有pop操作,而且最后需要逆序输出,太麻烦,所以可以直接用List替代s2栈List<String> s2 = new ArrayList<String>();// 遍历listfor (String item : list) {if (item.matches("\\d+")) {// 如果是一个数,则直接加入s2中s2.add(item);} else if (item.equals("(")) {s1.push(item);} else if (item.equals(")")) {// 如果是右括号,则依次弹出s1栈顶的元素,并加入s2中,直到遇到左括号为止,此时将这一对括号丢弃while (!s1.peek().equals("(")) {s2.add(s1.pop());}s1.pop();// 消除小括号} else {// 当item的优先级小于等于s1栈顶的优先级,将s1栈顶的运算符弹出并加入到s2中,再将item与s1中新的栈顶运算符相比较// 添加一个比较优先级高低的方法while (s1.size() != 0 && getValue(item) <= getValue(s1.peek())) {s2.add(s1.pop());}// 还需要将item压入栈中s1.push(item);}}// 将s1中剩余的元素符加入s2中while (s1.size() != 0) {s2.add(s1.pop());}return s2;}// 完成对逆波兰表达式的计算public static int calculate(List<String> list) {// 创建一个栈Stack<String> stack = new Stack<String>();// 遍历for (String item : list) {// 这里使用正则表达式取出数if (item.matches("\\d+")) {// 匹配的是多位数// 入栈stack.push(item);} else {// pop出两个数并计算,再入栈int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = 0;switch (item) {case "+":res = num1 + num2;break;case "-":res = num1 - num2;break;case "*":res = num1 * num2;break;case "/":res = num1 / num2;break;default:throw new RuntimeException("运算符有误");}// 把res入栈stack.push("" + res);// 把整数转换为字符串}}// 最终留在stack中的数据即为运算结果return Integer.parseInt(stack.pop());}//一个方法,用于返回优先级public static int getValue(String operator) {switch (operator) {case "+":return 1;case "-":return 1;case "*":return 2;case "/":return 2;case "(":return 0;default:System.out.println("不存在该运算符");break;}return 0;}
}

注: 仅支持加减乘除、括号的整数运算

Java数据结构之栈与计算器相关推荐

  1. Java数据结构之栈详解

    栈的定义: 栈(stack)是一种用于存储数据的简单数据结构.栈一个有序线性表,只能在表的一端(PS:栈顶)执行插人和删除操作.最后插人的元素将被第一个删除.所以,栈也称为后进先出(Last In F ...

  2. 【Java数据结构】栈和队列

    一. 栈(Stack) 1.概念 2.入栈和出栈的顺序 3.中缀表达式转后缀表达式 4.栈的方法 LeetCode 150. 逆波兰表达式求值 剑指 Offer 31. 栈的压入.弹出序列 5.栈的实 ...

  3. Java数据结构之栈的数组实现

    //数组模拟栈 public class ArrayStack {private int maxStacksize;private int[] stack;private int top = -1;p ...

  4. 头歌JAVA数据结构答案

    头歌JAVA数据结构答案 一.Java数据结构-循环链表的设计与实现 第1关 单循环链表的实现-链表的添加.遍历 package step1; /*** Created by sykus on 201 ...

  5. 【Java 数据结构】双向链表

    篮球哥温馨提示:编程的同时不要忘记锻炼哦! 圆圆的脑袋,大大耳朵,天天敲代码,找找找bug 目录 1.什么是双向链表 2.实现一个双向链表 2.1 实现前的约定 2.2 addFirst 方法 2.3 ...

  6. 【数据结构】栈:Java实现顺序栈栈应用浅析

    1.栈是什么 定义:后进者先出,先进者后出,这就是典型的"栈"结构 操作特性:栈是一种"操作受限"的线性表,只允许在一端插入和删除数据. 使用场景:当某个数据集 ...

  7. 数据结构:栈实现简易计算器

    文章目录 栈实现简易计算器 思路 代码实现 栈结构 运算方法 测试 栈实现简易计算器 之前的博客已经介绍了栈数据结构,栈有着数据先进后出的特点,因此用于实现简易计算器时相当方便.本博文中将介绍如何用栈 ...

  8. java语言链栈_Java语言实现数据结构栈代码详解

    近来复习数据结构,自己动手实现了栈.栈是一种限制插入和删除只能在一个位置上的表.最基本的操作是进栈和出栈,因此,又被叫作"先进后出"表. 首先了解下栈的概念: 栈是限定仅在表头进行 ...

  9. Java数据结构(1.1):数据结构入门+线性表、算法时间复杂度与空间复杂度、线性表、顺序表、单双链表实现、Java线性表、栈、队列、Java栈与队列。

    数据结构与算法入门 问题1:为什么要学习数据结构          如果说学习语文的最终目的是写小说的话,那么能不能在识字.组词.造句后就直接写小说了,肯定是不行的, 中间还有一个必经的阶段:就是写作 ...

最新文章

  1. MYSQL性能调优及架构设计学习笔记-基础篇MYSQL架构组成
  2. C标准库和glibc(C运行库)的关系
  3. redis分布式锁实现原理_redis分布式锁实现分析与实践
  4. 浏览器阻挡cookies_解决WordPress登录提示”Cookies被阻止或者您的浏览器不支持”...
  5. java Gregorian,Java GregorianCalendar getTimeZone()用法及代码示例
  6. 精华自取:神策 2019 数据驱动大会亮点回顾
  7. oppo5.0以上系统怎么样不Root激活Xposed框架的经验
  8. [Python / PyTorch] debug backward()
  9. ROSROS2可视化仿真软件
  10. 结构化数据与非结构化数据有什么区别?
  11. 通过aosp-latest下载Android源码
  12. 城市交通出行效率对比分析与思考
  13. grafana是什么?
  14. eos节点服务器_EOS柚子生态投票的骗局,你以为自己在区块恋革命,其实是在参与CX罢了...
  15. 中金人工智能报告:AI时代,10 年之后我们还能干什么?|71页完整报告
  16. 112、可燃液体的火灾危险性分类
  17. 用友T+“常用存货属性”
  18. React项目 antd 修改主题颜色
  19. The Django Book 网址
  20. 三星六月或推GALAXY S5高配版 为对抗G3

热门文章

  1. C语言:判断100-999中哪些数为水仙花数以及拓展应用
  2. wireshark使用及过滤器介绍
  3. 利用163 邮箱发送邮件
  4. 2022-2028年中国水玻璃行业竞争格局分析及投资发展研究报告
  5. Chrome流量监控
  6. C++语言篇 第一章 二进制及计算机基础
  7. 最简单的python语言程序设计_编程中最简单的语言Python,这样学或许更容易
  8. 真假屏幕测试软件,四个小技巧,检测 iPhone 屏幕是否为原装屏
  9. 爱思助手安卓能用吗_苹果的蓝牙耳机安卓能用吗
  10. html 圆圈图表插件,利用jquery html5实现图表动画圆形饼图