文章目录
一、栈的介绍
1)栈(stack)是一个先进后出(FILO)的有序列表;
2)栈是限制线性表中的元素只能从一端插入和删除的特殊线性表,允许插入和删除的一端为栈顶,固定的一端为栈底。
二、栈的使用场景
1.子程序调用;
2.处理 递归调用;
3.表达式的转换(中缀表达式转后缀表达式)与计算;
4.二叉树遍历;
5.图形的深度优先(depth-first)搜索法
三、栈的入门
由于栈是一种线性有序列表,因此可以使用数组模拟栈的pop、push操作
1、实现思路分析:
- 定义一个数组存放栈中的数据;
- 定义一个top指针执行栈顶元素,默认为-1;
- 入栈:先top++,让top指针向上移动一位,在将数据存放到指针所指的下标的位置;
- 出栈:先取出top所指向的下标的数据,再top–,将top向下移动一位,最后将取出的数据返回;
2、代码实现
package stack;
import org.junit.Test;
import java.util.Scanner;
/**
* 数组模拟栈
* 栈的特点,从栈顶push,栈顶pop,固定的一端为栈底。
*/
public class ArrayStackDemo {
public static void main(String[] args) {
System.out.printf("开始测试");
ArrayStack arrayStack = new ArrayStack(5);
String key ="";
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while(loop){
System.out.println("show:显示队列");
System.out.println("push:向队列添加元素");
System.out.println("pop :从队列取出元素");
key = scanner.next();//接收一个字符
switch (key) {
case "show":
arrayStack.showStack();
break;
case "push":
System.out.println("请输入要添加的数据");
int value = scanner.nextInt();
arrayStack.push(value);
break;
case "pop":
try {
int getValue = arrayStack.pop();
System.out.printf("取出的数据是:%d\n", getValue);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
class ArrayStack{
private int top= -1;//指定栈顶
private int maxSize;
private int[] stack;
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
this.stack = new int[maxSize];
}
/**
* 栈满
* @return
*/
public boolean isFull(){
return top==maxSize-1;
}
/**
* 栈空
* @return
*/
public boolean isEmpty(){
return top==-1;
}
/**
* 向栈里添加数据-入栈-push
* 先判断是否栈满,如果已满,则提示无法添加。
*/
public void push(int value){
if(isFull()){
System.out.println("栈满~~~");
return;
}
top++;
stack[top]=value;
}
/**
* 出栈
* 先判断是否栈空,如果为空,则抛出一场,提示栈空。
* @return
*/
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈空~~~");
}
int value = stack[top];
top--;
return value;
}
/**
* 打印栈中的每个数据
* 注意:出栈是从栈顶弹出
*/
public void showStack(){
if(isEmpty()){
System.out.println("栈空~~~");
return;
}
for(int i = top;i>-1;i--){
int value = stack[i];
System.out.println(value);
}
}
}
四、中缀表达式与后缀表达式
1、中缀表达式
像 “1+(2+3)*4” 这样便于人计算的表达式为中缀表达式;
中缀表达式实现计算器
package stack;
import com.sun.tools.corba.se.idl.InterfaceGen;
import java.util.Stack;
/**
* 用栈计算中缀表达式,实现计算器功能
*/
public class Calculate {
public static void main(String[] args) {
// String expression = "30+2*6-2";
String expression = "(5+2)*3-6*2+1";
//1、创建两个栈,分别存放中缀表达式中的数和操作符。
Stack<String> numStack = new Stack<String>();
Stack<String> operStack = new Stack<String>();
String keepNum = "";
//2. 创建index变量,用于从左到右遍历中缀表达式。
int index =0;
while (true){
//2.4 当index指针的值等于表达式长度,说明遍历结束
if(index==expression.length()){
break;
}
// 2.1 如果是一个数字,直接入numStack;
char val = expression.substring(index,index+1).charAt(0);
// 2.1 如果是一个数字,直接入numStack;
if(!isOper(val)){
//解决多位数运算问题:
//当遍历到一个字符,发现它是一个数字,则继续看看后一位,如果后一位还是数字,就将这个数组拼接在前一个数字后面并继续往后看,直到后一位是操作符为止。
//将最终拼接的数字字符串 push 到栈中.
keepNum = keepNum+val;
if(index == expression.length()-1){
numStack.push(keepNum);
}else{
if(isOper(expression.substring(index+1,index+2).charAt(0))){
numStack.push(keepNum);
keepNum="";
}
}
}else{
//2.2 如果是操作符,判断operStack栈中是否为空
//2.2.1 如果不是空,判断当前操作符的优先级是否<=operStack栈中的操作符
if(!operStack.empty()){
String oldOper = operStack.peek();//注意此处只是看一眼,没有真正取出
//2.2.1.1 如果当前操作符的优先级<=operStack栈中的操作符优先级
if(getPriority(val)<=getPriority(oldOper.charAt(0))){
// 1)从numStack弹出两个数,从operStack栈弹出一个数,并运算;
int num1 = Integer.parseInt(numStack.pop()) ;
int num2 = Integer.parseInt(numStack.pop());
int res = calculate(num1,num2,operStack.pop().charAt(0));
// 2)运算结果 push 到 numStack;
numStack.push(""+res);
// 3)将当前新的操作符 push到operStack;
operStack.push(""+val);
}else{
//2.2.1.2 如果当前操作符的优先级>operStack栈中的操作符优先级,直接入operStack栈
operStack.push(""+val);
}
}else{
operStack.push(""+val);
}
}
//2.3 index++,让index指针向后移动。
index++;
}
//3. 第一轮遍历完表达式后,operStack栈中剩下的操作符优先级都相同,按顺序遍历两个栈(循环以下操作)
while(true){
//3.3 当operStack中为空,说明计算结束
if(operStack.empty()){
break;
}
//3.1 从numStack中弹出两个数,从operStack栈弹出一个数,并运算;
char oper = operStack.pop().charAt(0);
int num1 = Integer.parseInt(numStack.pop()) ;
int num2 = Integer.parseInt(numStack.pop());
int res = calculate(num1,num2,oper);
//3.2 运算结果 push 到 numStack;
numStack.push(""+res);
}
//3.4 返回 numStack 中唯一的一个数,即为当前表达式最终运算结果
int finalRes = Integer.parseInt(numStack.pop());
System.out.println("最终计算结果为:"+finalRes);
//1、创建两个栈,分别存放中缀表达式中的数和操作符。
//2. 创建index变量,用于从左到右遍历中缀表达式。
//2.1 如果是一个数字,直接入numStack;(注意多位数的处理)
//2.2 如果是操作符,判断operStack栈中是否为空
//2.2.1 如果不是空,判断当前操作符的优先级是否<=operStack栈中的操作符
//2.2.1.1 如果当前操作符的优先级<=operStack栈中的操作符优先级
//》》》1)从numStack弹出两个数,从operStack栈弹出一个数,并运算;
//》》》2)运算结果 push 到 numStack;
//》》》3)将当前新的操作符 push到operStack;
//2.2.1.2 如果当前操作符的优先级>operStack栈中的操作符优先级,直接入operStack栈
//2.2.2 如果是空,直接入operStack栈
//2.3 index++,让index指针向后移动。
//2.4 当index指针的值等于表达式长度,说明遍历结束
//3. 第一轮遍历完表达式后,operStack栈中剩下的操作符优先级都相同,按顺序遍历两个栈(循环以下操作)
//3.1 从numStack中弹出两个数,从operStack栈弹出一个数,并运算;
//3.2 运算结果 push 到 numStack;
//3.3 当operStack中为空,说明计算结束
//3.4 返回 numStack 中唯一的一个数,即为当前表达式最终运算结果
}
public static boolean isOper(char val){
return val=='+' || val=='-' ||val=='*'||val=='/' ;
}
public static int getPriority(char val){
if(val=='(' || val==')'){
return 2;
}else if(val=='*' || val=='/'){
return 1;
}else if(val=='+' || val=='-'){
return 0;
}else{
return -1;
}
}
public static int calculate(int num1,int num2,int oper){
int res = 0;
switch (oper){
case '+':
res = num2+num1;
break;
case '-':
res = num2-num1;
break;
case '*':
res = num2*num1;
break;
case '/':
res = num2/num1;
break;
default:
break;
}
return res;
}
}
2、后缀表达式(逆波兰表达式)
像 “1 2 3 + 4 * +” 这样便于计算机使用的表达式为后缀表达式,后缀表达式的特点在于运算符在数字后。
3、中缀表达式转后缀表达式
1)初始化两个栈:存放运算符的栈 s1 和存放中间结果的栈 s2;
2)从左到右扫描中缀表达式;
3)如遇到操作数时:直接将其push到s2;
4)如遇到操作符时:要比较当前运算符与s1栈顶元素操作符的优先级:
(1)如果s1为空,或s1栈顶元素为“(” 时,直接将运算符push到s1;
(2)否则,如果当前运算符的优先级大于栈顶运算符的优先级,直接将运算符push到s1;
(3)否则(运算符优先级小于等于栈顶运算符优先级),先将s1栈顶运算符弹出,并push到s2中,然后再从4-1开始将当前运算符与新的栈顶运算符比较优先级。
5)如遇到小括号时:
(1)如果是“(”左括号,直接push到s1中;
(2)如果是“)”右括号,则将s1中的运算符pop出,并push到s2中,直到遇到一个“(”左括号为止,将这一对括号丢弃。
6)重复3至5步骤,直到将中缀表达式遍历完毕。
7)最后将s1中剩余的运算符一次pop出并push进s2中;
8)依次弹出s2中的元素,并输出,结果的逆序即为中缀表达式对应的后缀表达式。
中缀表达式转后缀表达式、并实现计算器功能
package stack;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
/**
* 后缀表达式(逆波兰表达式)实现逆波兰计算器
*
* 本篇最终实现功能:
* 给一个中缀表达式,将其转换成后后缀表达式并求计算结果。
*/
public class PolandNotation {
@Test
public void testParseToSufixExpress() {
String expression = "1+((2+3)*4)-5";
List<String> list = parseToSufixExpress(expression);
for(String str : list){
System.out.printf(str);
}
}
public static void main(String[] args) {
//(3+4)*5-6 -> 30 4 + 5 * 6 -
// //1、(改造前)逆波兰表达式的数字、符号之间用空格格开。将其转换成集合
// String expression = "3 4 + 5 * 6 -";
// List<String> list = convertToList(expression);
//1、(改造后)给一个中缀表达式,转成后缀表达式对应的list
String expression = "1+((2+3)*4)-5";
List<String> list = parseToSufixExpress(expression);
//2、创建一个栈,用于存放表达式里遍历出来的数
Stack<String> numStack = new Stack<String>();
int index = 0;//用于遍历表达式
//3、遍历list集合,判断当前的元素是数字还是操作符
for(String str : list){
if(!str.matches("\\d+")){
//3.1 如果是符号
// 则从numStack 中pop出两个数,并用当前运算符计算。
int num1 =Integer.parseInt(numStack.pop());
int num2 =Integer.parseInt(numStack.pop());
int res = calculate(num1,num2,str.charAt(0));
// 将计算结果存入numStack 中;
numStack.push(""+res);
}else{
//3.2 如果是数字入numStack;
numStack.push(str);
}
}
int finalRes = Integer.parseInt(numStack.pop());
System.out.println("最终计算结果为:"+finalRes);
}
/**
* 将中缀表达式转成后缀表达式
* 从左到右遍历中缀表达式,为了便于遍历,先将中缀表达式转成list。
*
* 创建一个栈 s1 用于存放中缀表达式集合中的运算符和左括号,创建一个list集合用于存放中间结果
* 从 sufixList 中遍历每一个元素:
* 1)如果是数字,直接添加到list集合中,
* 2)如果是运算符
* 2.1 如果s1栈中为空,或栈顶元素为左括号,直接将符号压入栈中;
* 2.2 否则,如果运算符的优先级高于栈顶运算符,直接将符号压入栈中;
* 2.3 否则,如果运算符的优先级小于等于栈顶运算符,将s1栈顶元素弹出,存到list中,并继续与下一个栈顶元素比较。
* 3)如果是括号:
* 3.1 如果是左括号,直接压入s1栈中;
* 3.2 如果是右括号,将s1 中的栈顶元素弹出,存到list中,直到遇到一个左括号,舍弃这一对括号。
*
* 4)循环遍历list,直到表达式list遍历完,最后将s1 栈中的元素,依次从栈顶弹出,存到list中,最后list中的元素顺序输出,就是后缀表达式了。
*
* @param expression 中缀表达式
* @return
*/
public static List<String> parseToSufixExpress(String expression){//expression = 1+((2+3)*4)-5
//从左到右遍历中缀表达式,为了便于遍历,先将中缀表达式转成list。
List<String> sufixList = parseToSufixList(expression);
Stack<String> s1 = new Stack<String>();
List<String> s2 = new ArrayList<String>();
for(String str : sufixList){
if(str.matches("\\d+")){
s2.add(str);
}else if("(".equals(str)){
s1.push(str);
}else if(")".equals(str)){
while(!s1.peek().equals("(")){
String value = s1.pop();
s2.add(value);
}
s1.pop();//弹出与有括号匹配的这个左括号;
}else{ //运算符
/*
* 2.1 如果s1栈中为空,或栈顶元素为左括号,直接将符号压入栈中;
* 2.2 否则,如果运算符的优先级高于栈顶运算符,直接将符号压入栈中;
* 2.3 否则,如果运算符的优先级小于等于栈顶运算符,将s1栈顶元素弹出,存到list中,并继续与下一个栈顶元素比较。
*/
if (s1.size()!=0 && !"(".equals(s1.peek()) && getPriority(str.charAt(0))<= getPriority(s1.peek().charAt(0))){
s2.add(s1.pop());
s1.push(str);
}else{
s1.push(str);
}
}
}
while(s1.size()>0){
s2.add(s1.pop());
}
return s2;
}
/**
* 将中缀表达式字符串转成对应的list
* 分析:
* 中缀表达式中可能存在多位数,因此要区分情况加入list
* 1)如果不是数字(运算符或括号)则直接加入list;
* 2)如果是数字,则要循环遍历表达式,向后找,将找到的数都拼起来,直到找到一个非数字的字符,结束遍历。
* @param expression
* @return
*/
public static List<String> parseToSufixList(String expression){
List<String> list= new ArrayList<String>();
int i=0;
String str ;//用于拼接多位数
char ch =' ';//用于接收从表达式中遍历到的字符
do{
if((ch=expression.charAt(i))<48 || (ch=expression.charAt(i))>57){ //1)如果不是数字(运算符或括号)则直接加入list;
list.add(""+ch);
i++;
}else{ //2)如果是数字,则要循环遍历表达式,向后找,将找到的数都拼起来,直到找到一个非数字的字符,结束遍历。
str = "";
while(i<expression.length() && (ch=expression.charAt(i))>=48 && (ch=expression.charAt(i))<=57){
str = str+ch;
i++;
}
list.add(str);
}
}while(i<expression.length());
return list;
}
public static List<String> convertToList(String expression){
String[] vals = expression.split(" ");
List<String> list = Arrays.asList(vals);
return list;
}
public static int calculate(int num1,int num2,int oper){
int res = 0;
switch (oper){
case '+':
res = num2+num1;
break;
case '-':
res = num2-num1;
break;
case '*':
res = num2*num1;
break;
case '/':
res = num2/num1;
break;
default:
break;
}
return res;
}
public static int getPriority(char val) {
if (val == '(' || val == ')') {
return 2;
} else if (val == '*' || val == '/') {
return 1;
} else if (val == '+' || val == '-') {
return 0;
} else {
return -1;
}
}
}
五、逆波兰计算器完整功能
package stack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
/**
* @author zhangxiaojuan@baoyinxiaofei.com
* @create 2020-07-28 7:33 PM
*/
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<String>();
//用于存放中间过程
static List<String> data = Collections.synchronizedList(new ArrayList<String>());
/**
* 去除所有空白符
* \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等,等价于[ \f\n\t\r\v]
* @param s
* @return
*/
public static String replaceAllBlank(String s){
return s.replaceAll("\\s+","");
}
/**
* 判断是否为数字
* @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;
}
/**
* 将中缀表达式转换成后缀表达式对应的list集合
* @param s
* @return
*/
public static List<String> doMatch(String s){
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())){
//如果栈非空,且当前运算符的优先级小于栈顶运算符的优先级,则从栈中弹出栈顶运算符并将其存入集合中,直到栈为空或者遇到一个左括号"("为止,最后将当前的运算度each存到栈中。
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(calcLevel(stack.peek()) == LEVEL_HIGH){ //是左括号时弹出左括号并结束循环
stack.pop();
break;
}
data.add(stack.pop());
}
}
start=i; //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");
}
}
//遍历完整个表达式之后,如果栈里还有表达式,则将其依次出栈入集合(即从栈顶出栈并添加到集合中,可以将栈反转,并将其转换成list,最后将整个集合添加到集合中)
Collections.reverse(stack);
data.addAll(new ArrayList<String>(stack));
System.out.println(data);
return data;
}
/**
* 计算结果
* @param list 后缀表达式对应的list集合
* @return
*/
public static Double doCal(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<String>();
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;
}
}
doCal(list1);
return d;
}
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";
doCal(doMatch(math));
}
}