栈及栈的应用举例

您所在的位置:网站首页 子程序的功能及应用场合 栈及栈的应用举例

栈及栈的应用举例

2024-07-16 03:53| 来源: 网络整理| 查看: 265

第5章 栈 1、前言2、栈的介绍和应用场景2.1 栈的基本介绍2.2 栈的应用场景 3、栈的思路分析及实现3.1 数组模拟实现栈3.2 链表模拟实现栈 4、栈实现计算器(中缀)4.1 思路分析4.2 代码实现 5、前缀 中缀 后缀表达式规则5.1 前缀表达式(波兰表达式)5.2 中缀表达式5.3 后缀表达式6、逆波兰表达式分析与实现 7、中缀转后缀表达式思路分析及实现7.1 思路分析7.2 代码实现7.3 完整版代码 实现

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) = 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 list = getListString(suffixExpression); System.out.println(list); int res = calculate(list); System.out.println("result = " +res); } //将后缀(逆波兰)表达式进行分割保存早list集合中 public static List getListString(String Expression){ String[] spilt = Expression.split(" "); List 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 list){ Stack 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 list = getListString(suffixExpression); System.out.println(list); int res = calculate(list); System.out.println("result = " +res);*/ String expression = "1+((2+3)*4)-5"; List infixExpressionList = toInfixExpressionList(expression); System.out.println("中缀表达式list集合"+infixExpressionList); List suffixExpressionList = parseSuffixExpressionList(infixExpressionList); System.out.println("后缀表达式list集合"+suffixExpressionList); int result = calculate(suffixExpressionList); System.out.println("result = " +result); } //将中缀表达式转换成对应的list public static List toInfixExpressionList(String s){ List list = new ArrayList(); int index = 0; String str ="" ; char ch; do{ //如果是非数字,需要加入list if( (ch = s.charAt(index)) 57){ list.add("" + ch); index++; }else{//数字 此时需要判断是否是多位数 while(index = 48 && (ch = s.charAt(index) )


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3