一、栈
1、概念
英文名stack,是一个先进后出的有序列表。插入和删除只能在栈顶进行操作,栈底固定。入栈push出栈pop
2、应用场景
1)子程序的调用:进入子程序之前,会先将下指令的程序地址存入栈中,子程序执行完成后,将地址取出,返回到原来的程序中。
2)处理递归调用:和子程序调用类似,只是除了程序地址之外,还会将参数、区域变量等存进栈中。
3)表达式的转换[中缀表达式转换后缀表达式]与求值
4)二叉树遍历
5)图形的深度优先搜索法
3、数组模拟栈,入栈,出栈等操作
/**
* @author chengxn
* @date 2020/10/11
* 数组模拟栈
*/
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack arrayStack = new ArrayStack(4);
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
while(loop){
System.out.println("show:循坏栈");
System.out.println("push:入栈");
System.out.println("pop:出栈");
System.out.println("exist:退出程序");
key = scanner.next();
switch(key){
case "push":
System.out.println("请输入数值:");
int value = scanner.nextInt();
arrayStack.push(value);
break;
case "pop":
try{
System.out.println(arrayStack.pop());
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case "show":
arrayStack.show();
break;
case "exist":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序结束");
}
}
class ArrayStack {
//栈的大小
private int maxSize;
private int[] stack;
private int top = -1;
/**
* 构造器
*
* @param maxSize
*/
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
this.stack = new int[maxSize];
}
/**
* 判读栈空
*
* @return
*/
private boolean isEmpty() {
return top == -1;
}
/**
* 判断栈满
*
* @return
*/
private boolean isFull() {
return top == maxSize - 1;
}
/**
* 入栈
*
* @param value
*/
public void push(int value) {
//判断栈满
if (isFull()) {
System.out.println("栈满了~~");
return;
} else {
top++;
stack[top] = value;
}
}
/**
* 出栈
*
* @return
*/
public int pop() {
//判断栈空
if (isEmpty()) {
throw new RuntimeException("栈空啦~没有数据");
} else {
int result = stack[top];
top--;
return result;
}
}
/**
* 遍历栈,因为只能在栈顶操作,所以从栈顶开始显示数据
*/
public void show(){
//判断为空
if(isEmpty()){
System.out.println("栈空,没有数据");
return;
}
for(int i = top; i > -1; i--){
System.out.println(stack[i]);
}
}
}
二、综合计算器实现
1、实现支持加减乘除+整数的计算器
代码实现的过程需要注意大于9的数字即长度超过2的数字!!!
import java.util.Stack;
/**
* 7*2*2-5+1-5+3-4=18
* @author chengxn
*
*/
public class Calculator2 {
public static void main(String[] args) {
String express = "13+2*16-2";
Stack numStack = new Stack();
Stack operStack = new Stack();
int index = 0;
char ch;
String keepnum = "";
while (true) {
ch = express.substring(index, index + 1).charAt(0);
if (Calculator2.isOper(ch)) {
//如果是操作符
if (!operStack.empty()) {
//不为空,判断运算符的优先级
if (Calculator2.priority(ch) <= Calculator2.priority((Character) operStack.peek())) {
//pop出两个数和一个运算符,计算结果,并入栈
int result = Calculator2.cal((Integer) numStack.pop(), (Integer) numStack.pop(), (Character) operStack.pop());
numStack.push(result);
}
}
operStack.push(ch);
} else {
//如果是数字,直接入栈 1的ascii码值是49
//代码实现的过程需要注意大于9的数字即长度超过2的数字
// numStack.push(ch-48); 若不考虑长度大于2的数字,这样写就支持了
keepnum += ch;
//判断是不是最后一位,
//是最后一位,直接入数栈
//不是最后一位要看下一位是不是数字
if (index == express.length() - 1) {
numStack.push(Integer.valueOf(keepnum));
} else {
//下一位是操作符,则入栈,并且清空keepnum,如果是数字,则继续循字符串
if (Calculator2.isOper(express.substring(index + 1, index + 2).charAt(0))) {
numStack.push(Integer.valueOf(keepnum));
keepnum = "";
}
}
}
index++;
if (index >= express.length()) {
break;
}
}
//顺序从数栈和符号栈取值进行计算
while (true) {
//符号栈为空,就结束计算
if (operStack.empty()) {
break;
}
int result = Calculator2.cal((Integer) numStack.pop(), (Integer) numStack.pop(), (Character) operStack.pop());
numStack.push(result);
}
System.out.println(express + "=" + numStack.peek());
}
/**
* 判断运算符优先级
*
* @param oper
* @return
*/
private static int priority(char oper) {
if (oper == '*' || oper == '/') {
return 1;
} else if (oper == '+' || oper == '-') {
return 0;
} else {
return -1;
}
}
/**
* 判断是否为运算符
*
* @param oper
* @return
*/
private static boolean isOper(char oper) {
return oper == '*' || oper == '/' || oper == '+' || oper == '-';
}
/**
* 计算
*
* @param num1
* @param num2
* @param oper
* @return
*/
private static int cal(int num1, int num2, char oper) {
int result = 0;
switch (oper) {
case '*':
result = num2 * num1;
break;
case '/':
result = num2 / num1;
break;
case '+':
result = num2 + num1;
break;
case '-':
result = num2 - num1;
break;
default:
break;
}
return result;
}
}
三、前缀表达式&中缀表达式&后缀表达式
1)前缀表达式(波兰表达式)
运算符位于操作数之前
例如:(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,计算得7入栈
3、遇到x,弹出7、5,计算得35入栈
4、遇到-,弹出35、6,计算得29为最终结果(最顶元素-次顶元素)
2)中缀表达式
就是我们常见的表达式
例如:(3+4)x5-6
对我们人来说是最熟悉的,但是对计算机来说不好操作,根据我们之前那个案例可以看出来,比如符号优先级,需要我们去判断,才能做运算。因此在计算结果时,一般是将中缀表达式转换为其他表达式(一般是后缀表达式)。
3)后缀表达式(逆波兰表达式)
运算符位于操作数之后
例如:(3+4)x5-6 对应的后缀表达式:3 4 + 5 x 6 -
后缀表达式计算机求值:
从左至右扫描表达式,遇到数字进栈,遇到运算符,弹出栈顶两个数,对他们做相应的运算,然后结果入栈,重复上述过程,直到表达式的最右端,最后的结果即为表达式的结果。
例如:(3+4)x5-6 后缀表达式: 3 4 + 5 x 6 –
1、从左至右,3 4入栈
2、遇到+,弹出4、3,计算得7入栈
3、遇到5,入栈
4、遇到x,弹出5、7,计算得35入栈
5、遇到6,入栈
6、遇到-,弹出6、35,计算得29为最终结果(次顶元素-最顶元素)
四、完成一个逆波兰计算器:支持小括号和整数
思路:
1、中缀表达式转换为后缀表达式
2、按照后缀表达式的规则计算
中缀表达式转后缀表达式思路:
中缀转后缀: (3+4)x5-6
1、索引index,从左往右,有两个栈,S1符号栈,S2中间结果栈
2、(1)如果是数字,直接入S2
(2)运算符,
a.S1为空/ S1栈顶为‘(’/优先级高于栈顶运算符,进S1
b.优先级小于等于栈顶运算符,栈顶运算符弹出进S2,继续进行(2)的判断
(3)如果四括号,
a.左括号,进S1
b.右括号,S1运算符依次弹出进S2,遇到左括号,将括号丢弃
3、按2将表达式遍历结束后,将S1依次弹出进S2
4、S2从栈底到栈顶即为后缀表达式结果
整个代码实现:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
/**
* 逆波兰计算器
* (3+4)*5-6
*
* @author chengxn
*/
public class PolandNotation {
public static void main(String[] args) {
String suffixExpression = "3 4 + 5 * 6 -";
List<String> strList = Arrays.asList(suffixExpression.split(" "));
Integer result = calculate(strList);
System.out.println("结果为:" + result);
// List<String> strList = turn("(3+4)*5-6");
// System.out.println("后缀表达式:" + strList);
// Integer result = calculate(strList);
// System.out.println("结果为:" + result);
}
/**
* 计算后缀表达式
*
* @param strList
* @return
*/
private static Integer calculate(List<String> strList) {
Stack<String> stack = new Stack();
for (String s : strList) {
if (s.matches("\\d+")) {
stack.push(s);
} else {
//pop出两个数并运算入栈
int num = cal(Integer.parseInt(stack.pop()), Integer.parseInt(stack.pop()), s);
stack.push(num + "");
}
}
return Integer.parseInt(stack.peek());
}
private static int cal(int num1, int num2, String oper) {
int result;
switch (oper) {
case "*":
result = num2 * num1;
break;
case "/":
result = num2 / num1;
break;
case "+":
result = num2 + num1;
break;
case "-":
result = num2 - num1;
break;
default:
throw new RuntimeException("运算符有误");
}
return result;
}
/**
* 中缀表达式转后缀表达式
*
* @param expression
* @return
*/
private static List<String> turn(String expression) {
expression = "(3+4)*5-6";
List<String> stringList = getExpressList(expression);
//s1符号栈 s2中间结果栈 因为后面s2并没pop操作,所以使用list
Stack<String> s1 = new Stack<>();
List<String> s2 = new ArrayList<>();
for (String s : stringList) {
//如果是数,入s2
if (s.matches("\\d+")) {
s2.add(s);
} else if (s.equals("(")) {
//左括号直接入s1
s1.push(s);
} else if (s.equals(")")) {
//如果是右括号,依次弹出s1的值进入s2,直到遇见左括号,将括号丢弃
while (!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop(); //丢弃左括号
} else {
// S1为空/'('/优先级高于栈顶运算符,进S1
//优先级小于等于栈顶运算符,栈顶运算符弹出进S2,继续进行判断
while (!s1.empty() && !s1.peek().equals("(") && Operation.getValue(s) < Operation.getValue(s1.peek())) {
s2.add(s1.pop());
}
s1.push(s);
}
}
//将s1依次弹出后入s2
while (!s1.empty()) {
s2.add(s1.pop());
}
return s2;
}
/**
* 中缀表达式转为list
*
* @param expression
* @return
*/
private static List<String> getExpressList(String expression) {
List<String> stringList = new ArrayList<>();
int index = 0;
String keepNum;
char c;
do {
//如果是非数字,就直接放入list
if ((c = expression.charAt(index)) < 48 || (c = expression.charAt(index)) > 57) {
stringList.add(c + "");
index++;
} else {
//数字,需要考虑多位数情况
keepNum = "";
while (index < expression.length() && (c = expression.charAt(index)) >= 48 && (c = expression.charAt(index)) <= 57) {
keepNum += c;
index++;
}
stringList.add(keepNum);
}
} while (index < expression.length());
return stringList;
}
}
class Operation {
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 2;
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:
System.out.println("运算符不存在");
break;
}
return result;
}
}