概述
1、栈(stack)是一个先入后出的有序列表
2、栈(stack)限制线性表中元素的插入和删除,只能在线性表的同一端进行操作的一种特殊线性表,允许插入和删除的一端是变化的一端,称为栈顶(Top),另一端是固定的一端称为栈底(Bottom);
3、新增元素:最先放入栈的元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
入栈示意图
元素出栈示意图
应用场景简要介绍
1、子程序的调用:在跳往子程序前,会先将下个指令的地址存入到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2、处理递归调用:和子程序类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入到堆栈中。
3、二叉树的遍历。
4、表达式的转换与求值(中缀表达式转后缀表达式)。
5、图的深度优先搜索法。
栈的代码实现(使用数组实现)
数组实现栈思路分析(先不考虑线程安全问题)
1、定义一个top表示栈顶,初始化为-1。
2、入栈的操作,当有数据入栈时, top ++ ,stack[top] = data
3、出栈的操作:临时变量 value = stack[top];top–;return value;
package com.example.data.sparse.stack;
/**
* @author zjt
* 数组模拟栈
*/
public class ArrayStack<E> {
private int maxSize; //表示栈的最大空间
private Object[] stack; // 数组模拟栈,数据放在数组中
private int top = -1; // 表示栈顶 初始化为-1
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
this.stack = new Object[maxSize];
}
// 判断栈是否已经满了
public boolean isFull() {
// 数组下标从0 开始
return top >= (maxSize - 1);
}
// 判断栈是否为空
public boolean isEmpty() {
return top == -1;
}
// 入栈
public void push(E e) {
// 判断栈是否已经满了
if (isFull()) {
System.out.println("栈已经满了");
return;
}
top++;
stack[top] = e;
}
// 出栈
public E pop() {
// 判断栈是否为空
if (isEmpty()) {
throw new RuntimeException("空栈");
}
Object value = stack[top];
top--;
return (E) value;
}
// 遍历栈 遍历时需要从栈顶开始显示数据
public void show() {
if (isEmpty()) {
System.out.println("空栈");
return;
}
for (int i = top; i >= 0; i--) {
System.out.println(i + "\t" + stack[i]);
}
}
}
class Client {
public static void main(String[] args) {
ArrayStack<Integer> stack = new ArrayStack<>(5);
stack.push(10);
stack.push(20);
stack.push(30);
stack.push(40);
stack.push(50);
stack.push(60);
stack.show();
System.out.println("出栈");
stack.pop();
stack.show();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
System.out.println("出栈");
stack.push(60);
stack.show();
}
}
栈也可以使用链表方式实现,使用头插法、头删法使用会简单一些。
案例
案例一 栈实现表达式计算:比如1+2+3*4-5的值。
思路
1、我们需要两个栈,一个是数栈,用来存放数字。一个是符号栈,用来存放运算符。
2、通过index索引,来遍历我们的表达式。
3、如果发现是数字就直接存放到数栈。
4、如果发现扫描到一个符号,就要分情况讨论:
4.1如果当前符号栈为空,就直接入栈。
4.2如果符号栈有操作符,就进行比较,如果当前操作符号的优先级小于或者等于栈中的操作符号,就需要从数栈中pop出两个数字,然后从符号栈中pop出一个符号进行运算,将得到的结果入数栈,然后将当前的操作符入符号栈。如果当前的操作符号的优先级大于栈中的操作符号,就直接入栈。
5、当表达式扫描完毕后,就顺序的从数栈和符号栈pop出相应的数和符号并运行。
6、最后在数栈只有一个数字,就是表达式的结果。
栈中的扩展功能
// 查看当前栈顶的值,但是不出栈,只是偷看一眼
public E peek() {
if (isEmpty()) {
throw new RuntimeException("空栈");
}
return (E) stack[top];
}
// 扩展功能 返回运算符的优先级
// 这里定义 数字越大 优先级越高
public int priority(char opera) {
switch (opera) {
case '*':
case '/':
return 1;
case '+':
case '-':
return 0;
default:
return -1; // 假定表达式仅有 者四个运算符号 返回 -1 表示符号有问题
}
}
// 判断是否是操作符号
public boolean isOpera(char c) {
return oprList.contains(c);
}
// 计算方法
public int cal(int num1, int num2, char opera) {
switch (opera) {
case '+':
return num1 + num2;
case '-':
return num2 - num1; // 这里需要注意顺序,原因是:先入栈的后出栈 对应 减数与被减数。除法也一样
case '*':
return num1 * num2;
case '/':
return num2 / num1;
default:
throw new RuntimeException("操作符号有误");
}
}
// 计算方法
public int cal1(int num1, int num2, char opera) {
switch (opera) {
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
return num2 / num1;
default:
throw new RuntimeException("操作符号有误");
}
}
计算逻辑
// 栈之前已经实现,直接使用 扩展一下功能
class Calculator {
public static void main(String[] args) {
// 表达式
String expression = "7-3+4*3/3-2-1-1";
// 书栈
ArrayStack<Integer> numStack = new ArrayStack<>(10);
// 符号栈
ArrayStack<Character> operaStack = new ArrayStack<>(10);
// 定义变量 index用于扫描 其它是临时变量
int index = 0, num1 = 0, num2 = 0, result = 0;
char opera; // 运算符
char ch = ' ';// 将每次扫描得到的char 保存到临时变量中
// 开始扫描
do {
// 依次得到表达式中每一个字符
ch = expression.substring(index, index + 1).charAt(0);
// 判断ch是什么然后做相应的处理
if (operaStack.isOpera(ch)) {
if (operaStack.isEmpty()) { // 符号栈是空栈
operaStack.push(ch);
} else {
// 如果符号栈有操作符,就进行比较,如果当前操作符号的优先级小于或者等于栈中的操作符号,
// 就需要从数栈中pop出两个数字,然后从符号栈中pop出一个符号进行运算,将得到的结果入数栈,
// 然后将当前的操作符入符号栈。
// 如果当前的操作符号的优先级大于栈中的操作符号,就直接入栈
if (operaStack.priority(ch) <= operaStack.priority(operaStack.peek())) {
//优先级小于或者等于栈中的操作符号
num1 = numStack.pop();
num2 = numStack.pop();
opera = operaStack.pop();
result = numStack.cal(num1, num2, opera);
// 将得到的结果入数栈
numStack.push(result);
operaStack.push(ch);
} else {
operaStack.push(ch);
}
}
} else {
// 如果直接是数字 就直接入栈
numStack.push(Integer.parseInt(String.valueOf(ch)));
}
index++;
} while (index < expression.length());
// 当表达式扫描完毕后,获取到两个栈 栈中的计算与真实计算刚好相反 原因是 后入栈的先出栈导致反着计算
// 所以会出现 1-1*1+9 = -9 的情况 这时 数栈中存储 1 1 9 符号栈中存储 - + 按照栈的规则计算是 1+9 1-10 的错误
// 数栈
// ArrayStack<Integer> numStack1 = new ArrayStack<>(10);
// // 符号栈
// ArrayStack<Character> operaStack1 = new ArrayStack<>(10);
// 进行栈的反序列
// for (;;){
// if (operaStack.isEmpty()) {
// break;
// }
// operaStack1.push(operaStack.pop());
// }
// for (;;){
// if (numStack.isEmpty()) {
// break;
// }
// numStack1.push(numStack.pop());
// }
while (true) {
// 如果符号栈为空,则计算得到最后的结果
if (operaStack.isEmpty()) {
System.out.println(String.format("表达式 %s = %d", expression, numStack.pop()));
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
opera = operaStack.pop();
result = numStack.cal1(num1, num2, opera);
numStack.push(result);
}
}
}