对栈的学习(前缀、中缀、后缀表达式)超详细!由计算器的实现哦
栈
栈的概念
1)栈的英文为(stack)
2)栈是一个先入后出(FILO-First In Last Out)的有序列表。
3)栈(stack)是限制线 性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另中一端为固定的一 端,称为栈底(Bottom)。
4)根据栈的走义可知,最先放入栈中元素在栈底,最后放入的元素在栈项,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
栈的应用场景
1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3)表达式的转换 [中缀表达式转后缀表达式]与求值(实际解决)。
- 二叉树的遍历。
5)图形的深度优先(depth一first)搜索法。
用数组模拟栈
代码实现
package com.zhao.stack;public class ArrayStackDemo {public static void main(String[] args) {ArrayStack stack = new ArrayStack(50);for (int i=1;i<20;i++){stack.push(i);}stack.pop();stack.pop();stack.pop();stack.pop();stack.list();}
}//定义一个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;}//入栈public void push(int value){if (isFull()){System.out.println("栈已经满了");return;}top++;stack[top] = value;}//出栈public int pop(){if (isEmpty()){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]);}}
}
用栈实现综合计算器(中缀表达式)
图解思路
代码实现
package com.zhao.stack;import javax.lang.model.element.NestingKind;public class Calculator {public static void main(String[] args) {String expression = "80+2*6-2";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 aChar = ' '; //将每次扫描得到char保存到chString keepNum ="";//用于拼接多位数//开始扫描字符串while(true){//依次得到expression的每一个字符aChar = expression.substring(index,index+1).charAt(0);//判断aCher是什么if (operStack.isOper(aChar)) {//如果是运算符//判断当前的符号栈是否为空if (!operStack.isEmpty()) {//如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数。//在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈if (operStack.priority(aChar) <= operStack.priority(operStack.peek())) {num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();res = numStack.cal(num1, num2, oper);//把运算结果入数栈numStack.push(res);//然后将当前的操作符入符号栈operStack.push(aChar);} else {//如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈。operStack.push(aChar);}} else {//如果为空直接入符号栈operStack.push(aChar);}}else {//如果是数,则直接入数栈//numStack.push(aChar - 48);//因为取出来的时字符串”1“//如果是一个多位数,不能发现一个数就立即入栈//分析思路//1.当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数//2.在处理数,需要向expression的表达式的index后再看一位,如果是数就进行扫描,如果是符号才入栈//3.因此我们需要定义一个变量字符串,用于拼接//处理多位数keepNum += aChar;//如果是最后一位则不需要判断,直接入栈if (index == expression.length()-1){numStack.push(Integer.parseInt(keepNum));keepNum = "";}else {//判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈//注意是看后一位,不是index++if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {//如果后一位是运算符,则入栈numStack.push(Integer.parseInt(keepNum));//keepNum清空keepNum = "";}}}//让index+1,并判断是否扫描到最后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,res);}}//定义一个ArrayStack 表示栈
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];}//查看栈顶的方法public 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()){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;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)前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
2)举例说明: (3+4)X5-6 对应的前缀表达式就是 - X + 3 4 5 6
前缀表达式的计算机求值
从有至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈项的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
例如: (3+4)X5-6对应的前缀表达式就是- x + 3 4 5 6
,针对前缀表达式求值步骤如下:
1)从右至左扫描,将6、5、4、3压入堆栈
2)遇到+
运算符,因此弹出3和4 (3为栈顶元素,4为次顶元素),计算出3+4
的值,得7,再将7入栈
3)接下来是x
运算符,因此弹出7和5, 计算出7x5=35
,将35入栈
4)最后是-
运算符,计算出35-6的值,即29,由此得出最终结果
中缀表达式
1)中缀表达式就是常见的运算表达式,如(3+4)X5-6
2) 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作
后缀表达式
1)后缀表达式又称逆波兰表达式与前缀表达式相似,只是运算符位于操作数之后
2)举例说明: (3+4)X5-6 对应的后缀表达式就是34+5 X 6-
后缀表达式的计算机求值
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
例如: (3+4)X5-6对应的后缀表达式就是3 4 + 5 X 6 -
,针对后缀表达式求值步骤如下:
1)从左至右扫描,将3和4压入堆栈;
2)遇到+
运算符, 因此弹出4和3 (4为栈项元素,3为次项元素),计算出3+4的值,得7,再将7入栈;
3)将5入栈;
4) 接下来是x
运算符,因此弹出5和7,计算出7x5=35
,将35入栈;
5) 将6入栈;
6) 最后是-
运算符,计算出35-6
的值, 即29, 由此得出最终结果
逆波兰计算器
我们完成一个逆波兰计算器,要求完成如下任务:
1)输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果
2)支持小括号和多位数整数,因为这里我们主要学的是数据结构,因此计算器进行简化,只支持对整数的计算。
3)思路分析
例如: (3+4)X5-6对应的后缀表达式就是
3 4 + 5 X 6 -
,针对后缀表达式求值步骤如下: 1)从左至右扫描,将3和4压入堆栈;
2)遇到
+
运算符, 因此弹出4和3 (4为栈项元素,3为次项元素),计算出3+4的值,得7,再将7入栈; 3)将5入栈;
4) 接下来是
x
运算符,因此弹出5和7,计算出7x5=35
,将35入栈; 5) 将6入栈;
6) 最后是
-
运算符,计算出35-6
的值, 即29, 由此得出最终结果
4)代码完成
package com.zhao.stack;import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public class PolandNotation {public static void main(String[] args) {//先定义给逆波兰表达式//(3+4)x5-6 => 34+5x6-=> 29//说明为了方便,逆波兰表达式的数字和符号使用空格隔开String suffixExpression = "30 4 + 5 * 6 -";//思路//1.先将"(3+4)+5x6" =>放到ArrayList中//2.将ArrayList传递给一个方法, 遍历ArrayList配合栈完成计算List<String> stringList = getListString(suffixExpression);System.out.println(stringList);int calculate = calculate(stringList);System.out.println("计算结果:"+calculate);}//将一个逆波兰表达式,依次将数据和运算符放入到ArrayList中public static List<String> getListString(String suffix){//将suffix分割String[] strings = suffix.split(" ");List<String> list = new ArrayList<>();for (String s:strings){list.add(s);}return list;}/*** 完成对逆波兰表达式的计算* 例如: (3+4)X5-6对应的后缀表达式就是`3 4 + 5 X 6 -`,针对后缀表达式求值步骤如下:* 1)从左至右扫描,将3和4压入堆栈;* 2)遇到`+`运算符, 因此弹出4和3 (4为栈项元素,3为次项元素),计算出3+4的值,得7,再将7入栈;* 3)将5入栈;* 4) 接下来是`x`运算符,因此弹出5和7,计算出`7x5=35`,将35入栈;* 5) 将6入栈;* 6) 最后是`-`运算符,计算出`35-6`的值, 即29, 由此得出最终结果*/public static int calculate(List<String> list){//创建一个栈Stack<String> stack = new Stack<>();//遍历listfor (String item : list){//使用正则表达式来去出数if (item.matches("\\d+")){//匹配的是多位数//入栈stack.push(item);}else {//pop取出两个数,并运算,在入栈int n1 = Integer.parseInt(stack.pop());int n2 = Integer.parseInt(stack.pop());int res = 0;switch (item){case "+":res = n2 + n1;break;case "-":res = n2 - n1;break;case "*":res = n2 * n1;break;case "/":res = n2 / n1;break;default:new RuntimeException("由特殊符号");break;}stack.push(""+res);}}return Integer.parseInt(stack.pop());}
}
中缀表达式 转化为 后缀表达式
大家看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。
具体步骤如下:
- 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
- 从左至右扫描中缀表达式;
- 遇到操作数时,将其压s2;
- 遇到运算符时,比较其与s1栈项运算符的优先级:
- 如果s1为空,或栈项运算符为左括号“(”, 则直接将此运算符入栈;
- 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
- 否则,将s1栈项的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
- 遇到括号时:
- 如果是左括号“(”,则直接压入s1
- 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
- 重复步骤2至5,直到表达式的最右边
- 将s1 中剩余的运算符依次弹出并压入s2
- 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
图解:
package com.zhao.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)x4)-5 => 转成 123+4x+5-//2.因为直接对str. 进行操作,不方便,因此先将"1+((2+3)x4)-5" =》中缀的表达式对应的List//即"1+((2+3)x4)-5" => ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]//3.将得到的中缀表达式对应的List =>后缀表达式对应的List//即ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,-]String expression = "1+((2+3)*4)-5";List<String> list = toInfixExpressionList(expression);System.out.println(list);List<String> suffixList = parseSuffixList(list);System.out.println(suffixList);int calculate = calculate(suffixList);System.out.println(calculate);/*//先定义给逆波兰表达式//(3+4)x5-6 => 34+5x6-=> 29//说明为了方便,逆波兰表达式的数字和符号使用空格隔开String suffixExpression = "30 4 + 5 * 6 -";//思路//1.先将"(3+4)+5x6" =>放到ArrayList中//2.将ArrayList传递给一个方法, 遍历ArrayList配合栈完成计算List<String> stringList = getListString(suffixExpression);System.out.println(stringList);int calculate = calculate(stringList);System.out.println("计算结果:"+calculate);*/}//方法:将得到的中缀表达式对应的List =>后缀表达式对应的Listpublic static List<String> parseSuffixList(List<String> ls){//定义两个栈Stack<String> s1 = new Stack<>();//说明:因为s2这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出//因此比较麻烦,这里我们就不用Stack<String>直接使用List<String> s2//Stack<String> s2 = new Stack<String>(); //储存中间结果的栈s2List<String> s2 = new ArrayList<>();//遍历lsfor (String item : ls){if (item.matches("\\d+")){//多位数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中,再次转到(4.1)与s1中新的栈顶运算符相比较while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item) ){s2.add(s1.pop());}s1.push(item);}}//将s1中剩余的运算符依次弹出并加入s2while (s1.size() != 0){s2.add(s1.pop());}return s2;}//方法:将中缀表达式转成对应的Listpublic static List<String> toInfixExpressionList(String s){//定义一个List,存放中缀表达式对应的内容List<String> ls = new ArrayList<>();int i = 0;//这是一个指针,用于遍历中缀表达式字符串String str;//对多位数的拼接char c;//没遍历到一个字符,就放入到cdo {//如果c是一个非数字,需要加入到lsif ((c=s.charAt(i)) < 48 || (c=s.charAt(i)) > 57 ){ls.add(""+c);i++;//i后移}else {//如果是一个数,要考虑多位数str = "";if (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 suffix){//将suffix分割String[] strings = suffix.split(" ");List<String> list = new ArrayList<>();for (String s:strings){list.add(s);}return list;}/*** 完成对逆波兰表达式的计算* 例如: (3+4)X5-6对应的后缀表达式就是`3 4 + 5 X 6 -`,针对后缀表达式求值步骤如下:* 1)从左至右扫描,将3和4压入堆栈;* 2)遇到`+`运算符, 因此弹出4和3 (4为栈项元素,3为次项元素),计算出3+4的值,得7,再将7入栈;* 3)将5入栈;* 4) 接下来是`x`运算符,因此弹出5和7,计算出`7x5=35`,将35入栈;* 5) 将6入栈;* 6) 最后是`-`运算符,计算出`35-6`的值, 即29, 由此得出最终结果*/public static int calculate(List<String> list){//创建一个栈Stack<String> stack = new Stack<>();//遍历listfor (String item : list){//使用正则表达式来去出数if (item.matches("\\d+")){//匹配的是多位数//入栈stack.push(item);}else {//pop取出两个数,并运算,在入栈int n1 = Integer.parseInt(stack.pop());int n2 = Integer.parseInt(stack.pop());int res = 0;switch (item){case "+":res = n2 + n1;break;case "-":res = n2 - n1;break;case "*":res = n2 * n1;break;case "/":res = n2 / n1;break;default:throw new RuntimeException("由特殊符号");}stack.push(""+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 result = 0;switch (operation){case "+":result = ADD;break;case "-":result = SUB;break;case "*":result = MUL;break;case "/":result = DIV;break;default:break;}return result;}
}
对栈的学习(前缀、中缀、后缀表达式)超详细!由计算器的实现哦相关推荐
- python【数据结构与算法】表达式(前缀中缀后缀表达式)与Two fork tree
文章目录 1 相关概念 2 与二叉树关系 3 表达式转换 4 另一种方法 1 相关概念 前缀表达式(Prefix Notation)是指将运算符写在前面操作数写在后面的不包含括号的表达式,而且为了纪念 ...
- 数据结构:前缀,中缀,后缀表达式(逆波兰表达式)
前缀表达式(波兰表达式) 前缀表达式的运算符位于操作数之前. 比如 (1+2)*3-4 对应的前缀表达式就是: - * + 1 2 3 4 前缀表达式的计算机求值 从右至左扫描表达式,遇到数字时,就 ...
- 前缀中缀后缀表达式的计算求值
原文在这里 表达式 前缀表达式(波兰表达式) 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前 举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 前缀表达式求值 ...
- 表达式树前缀中缀后缀表达式
表达式树( expression tree ), 表达式树的树叶是操作数( operand ),比如常数或变量,而其他的节点为操作符( operator )如: 由于这里所有的操作都是二元的,因此这棵 ...
- 前缀 中缀 后缀表达式2
对于未经训练的用户来说,计算机科学领域中数学表达式求值的传统方法即不顺手又难以使用:软件工程师 Nikola.Stepan 旨在改变这些传统方法.他的 applet W3Eval 对表达式求值与您用纸 ...
- 前缀中缀后缀表达式介绍
一 前缀表达式 1 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前. 2 举例 (3+4)×5-6 对应的前缀表达式是: - × + 3 4 5 6 3 前缀表达式的计算机求值过程 从右至左扫 ...
- 前缀 中缀 后缀表达式
1.前缀表达式叫波兰式,后缀叫逆波兰式 2.中缀表达式转另外两个比较简单,前后缀装中缀较麻烦 3.问题分求表达式还是求值,如果是求值则需要两个栈,一个是操作符栈,一个是操作数栈,等操作符栈入栈完毕后依 ...
- C#数据结构-前缀中缀后缀+中缀转后缀
目录 一.前缀中缀后缀表达式 1.中缀表达式 2.前缀表达式 3.后缀表达式 二.中缀转后缀 一.前缀中缀后缀表达式 1.中缀表达式 中缀表达式就是平常生活中计算式子的写法,例如:(3+4)*5-6 ...
- 中缀表达式转换为前缀及后缀表达式并求值【摘】
它们都是对表达式的记法,因此也被称为前缀记法.中缀记法和后缀记法.它们之间的区别在于运算符相对与操作数的位置不同:前缀表达式的运算符位于与其相关的操作数之前:中缀和后缀同理. 举例: (3 + 4) ...
- 中缀表达式转换为前缀或后缀表达式的手工做法
以 a/b + ( c*d - e*f) / g 为例: 步骤1:按照运算符的优先级对所有的运算单元加括号. ((a/b) + (( (c*d) - (e*f)) / g)) 步骤2:转换为前缀或后缀 ...
最新文章
- 加速企业数字化转型,首届Spring Summit技术峰会圆满落幕
- Ubuntu 16.04 LTS今日发布
- IIS7 设置读取、脚本和可执行文件的执行权限的步骤
- php libmysqlclient,什么是php?以及mysqlnd与libmysqlclient
- 简单入门——深度学习笔记(Part II)
- 2016中国APP分类排行榜发布暨颁奖晚宴 —— 兰亭修禊少长有王谢 黔香阁暖高见望诸公...
- microsoft project 出现不能保存为xls文件时可以按照如下方法解决
- 生产Docker应用重启排查经历
- spring源码分析第六天------spring经典面试问题
- Mac安装软件报“打不开。。。,因为它来自身份不明的开发者”的解决办法
- 云计算之路-阿里云上:用上了开放缓存服务OCS
- 【 ORA-27102: out of memory处理】
- android 表情删除,微信8.0版本为什么删除30多个表情?旧表情删除原因说明[多图]...
- [清华集训2016]温暖会指引我们前行——LCT+最大生成树
- nosql的base和cap_关系型数据库遵循ACID规则 NoSQL 数据库BASE CAP
- 每日一句20191224
- EJB到底是什么,真的那么神秘吗??
- 手把手教你如何将图片“嵌入”网页中
- 外汇会计-概念-即期汇率
- PS新手如何快速抠白色背景的玻璃瓶
热门文章
- java做类似于qq空间动态加载_Android仿QQ空间动态界面分享功能
- C语言/c++:实验报错[Error] ld returned 1 exit status的解决方案
- redis连接异常 redis.clients.jedis.exceptions.JedisClusterException CLUSTERDOWN The cluster is down
- 无数个骂娘的故事告诉你,千万别做技术合伙人
- Druid【部署 01】最新版本的(安装+启动服务+打开控制台+本地文件数据载入+数据查询)及(小内存服务器启动JVM参数调整)一次学会使用Druid的完整流程(已完成)
- 全国大学生数学建模竞赛——2017A题(学习笔记)
- 屏蔽高通8953长按电源键的截屏按钮
- 2022-03-25 redis哨兵处理failover
- FineBI中tomcat服务器部署
- 面向开发的内存调试神器,如何使用ASAN检测内存泄漏、堆栈溢出等问题