1.6 栈
1.6.1 了解栈
- 栈的英文为(stack)
- 栈是一个后进先出(FILO-First In Last Out)的有序列表。
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的 一端,为
变化的一端,称为栈顶
(Top),另一端为固定的一端,称为栈底
(Bottom)。 - 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
- 图解方式说明
出栈(pop)和入栈(push)
的概念
栈的应用场景:
栈作为一种重要的基本数据结构,它的应用是比较广泛的。栈的应用包括如下几个方面:
- 子程序的调用:在跳往子程序前, 会先将下个指令的地址存到堆栈中, 直到子程序执行完后再将地址取出, 以 回到原来的程序中;
- 处理递归调用:和子程序的调用类似, 只是除了储存下一个指令的地址外, 也将参数、 区域变量等数据存入堆 栈中;
- 表达式的转换(中缀表达式转后缀表达式)与求值;
- 二叉树的遍历;
- 图形的深度优先(depth 一 first)搜索法。
1.6.2 栈的简单数组实现
思路:
使用数组来模拟栈的思路是比较简单的,按照下面的步骤即可
- 定义一个类,该类的成员变量包括一个数组 stack(用于模拟栈)、两个整型变量 maxSize、top(分别代表栈的大小、栈顶指针);
- 栈顶指针 top 初始化为 -1;
- 每当有元素要入栈时,top 加 1,然后元素记录在数组中,即 stack[top] = element;
- 每当有元素要出栈时,先读取数组的元素,即 element = stack[top],然后 top 减 1。
实现代码:
import java.util.Scanner;
//1. 定义一个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;
}
//入栈--push
public void push(int value) {
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈-pop,将栈顶的数据返回
public int pop() {
if (isEmpty()) throw 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 class stackDemo {
public static void main(String[] args) {
//测试一下ArrayStack 是否正确
//先创建一个ArrayStack对象->表示栈
ArrayStack stack = new ArrayStack(4);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while (loop) {
System.out.println("show: 表示显示栈");
System.out.println("exit: 退出程序");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.printf("出栈的数据是 %d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
1.6.3 栈实现简单计算器(中缀表达式)
需求:
思路分析:
-
使用两个栈,一个作为数栈(用于存放计算需要的数字),一个作为符号栈(一个用于存放运算需要的运算符)
-
使用一个index索引来遍历我们的表达式,当扫描到数字入数栈,扫描到符号入符号栈
-
符号优先级设置是自己设置的,优先级数字越大优先级越大,注意 - 的优先级要比 **+ **的优先级要大,否则在计算类似(a-b+c)的表达式的时候会出现先b+c = d后a-d的情况
-
入数栈的细节:
- 需要判断该数字字符的下一位是否还是数字字符,如果是的话记得拼接为多位的数字(需要注意当数字字符为表达式最后一位的情况)
-
入符号栈的细节:
- 1.当符号栈为空的时候,直接将符号入栈
- 2.当符号栈不为空的时候
- 若当前符号的优先级小于栈中的符号,先从数栈中出栈2个数num1 ,num2。符号栈出栈一个符号p1 进行运算,得到运算结果result,之后将result入数栈,(再次判断当前字符与符号栈顶的字符优先级,小于则再次计算,直到当前字符优先级大于或等于符号栈顶的字符时)才能将当前符号入符号栈
- 若当前符号的优先级大于栈中的符号,直接将符号入符号栈
-
当表达式扫描完毕的时候,对数栈和符号栈依次进行数栈出栈2个,符号栈出栈1个 进行运算,将结果入数栈的过程。直到符号栈为空。这个时候,数栈也只会留下一个值,该值就是最后的表达式运算结果
-
注意:因为数栈和符号栈使用的是同一个栈类并且将stack数组定义为了int 所以本代码无法实现小数运算。想要将代码该改为支持小数运算,①可以将栈类改造为支持泛型,根据传入类型而变换数组类型。或者 ② 额外定义一个栈类给数栈和符号栈分别使用
实现代码与输出结果:
class ArrayStack02 {
private int maxSize;//栈的大小
private int[] stack; //数组,数组模拟栈
private int top = -1; //top表示栈顶,初始化为-1
public ArrayStack02(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
public int lookNext(){ // 返回栈顶数据 但不会pop出值
if (isEmpty()) {
throw new RuntimeException("栈空!");
}
return stack[top];
}
//栈满
public boolean isFull() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈--push
public void push(int value) {
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈-pop,将栈顶的数据返回
public int pop() {
if (isEmpty()) throw 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 class Calculator {
public static int checkOrder(char ch){ // 返回运算符的优先级
switch (ch) {
case '/':
case '*' : return 2;
case '-': return 1;
case '+' : return 0;
default : return -1;
}
}
public static boolean isOperator(char ch){ // 判断字符是否为运算符
return ch == '*'|| ch=='/' || ch=='+'||ch=='-';
}
public static int countNum(int num1,int num2,char ch){ // 计算数栈与符号栈的计算结果值
switch (ch){
case '+' : return num1+num2;
case '-' : return num2-num1;
case '*' : return num1*num2;
case '/' : return num2/num1;
default : throw new IllegalStateException("Unexpected value: " + ch);
}
}
/**
* 1.若符号栈为空,直接入栈
* 2.若符号栈不为空,判断优先级,若当前符号优先级高 那么直接入栈
* 3.若当前符号优先级较低 那么从符号栈pop一个 数栈pop2个值 计算结果入数栈 然后才将当前操作符入符号栈
*
* 1. 在读取是数字字符时,拼接到keepNum上
* 2. 判断下一个字符是不是还是数字字符 是,继续遍历 不是,将keepNum转为数字入数栈
* 3. 注意要小心字符串的最后一个字符是无法查看下一个字符的
*/
public static void countMachine(String countString){
ArrayStack02 stack01 = new ArrayStack02(20); // 数栈
ArrayStack02 stack02 = new ArrayStack02(20); // 符号栈
int len = countString.length();
int index = 0;
int num1,num2,numEnd;
char p1,ch;
String keepNum = ""; // 用于拼接数字字符
while (index < len){
ch = countString.substring(index,index+1).charAt(0); // 遍历字符串得到字符ch
if(isOperator(ch)){ // 为操作符
// 判断符号栈是否为空
if (stack02.isEmpty()){
stack02.push(ch);
System.out.println("stack01: push-> "+ch);
}else{
while (true){
// 优先级较小的情况
if(checkOrder(ch) < checkOrder((char) stack02.lookNext())){
p1 = (char) stack02.pop();
System.out.println("stack02: pop-> "+p1);
num1 = stack01.pop();
System.out.println("stack01: pop-> "+num1);
num2 = stack01.pop();
System.out.println("stack01: pop-> "+num2);
numEnd = countNum(num1, num2, p1);
stack01.push(numEnd);
System.out.println("stack01: push-> "+numEnd);
// 注意:连环高优先级的情况 如:a*b*c+c 需要多判断几层
if(stack02.isEmpty() || checkOrder(ch) > checkOrder((char) stack02.lookNext())){
stack02.push(ch);
System.out.println("stack02: push-> "+ch);
break;
}
}else{// 优先级较大 直接入栈
stack02.push(ch);
System.out.println("stack02: push-> "+ch);
break;
}
}
}
}else { // 为数字字符
keepNum = keepNum + ch;
// 当前字符是否为最后一个
if(index == len-1){
stack01.push(Integer.parseInt(keepNum));
System.out.println("stack01: push-> "+keepNum);
}else { // 不为最后一个字符 查看下一个字符是否为数字字符
if (isOperator(countString.substring(index+1,index+2).charAt(0))){ // 下一个字符为操作符
stack01.push(Integer.parseInt(keepNum));
System.out.println("stack01: push-> "+keepNum);
// 记得一定要把keepNum清空
keepNum = "";
}
}
}
index++;
}
// 计算 清空字符栈
while (!stack02.isEmpty()){ // 当字符栈为空时 循环结束
p1 = (char) stack02.pop();
num1 = stack01.pop();
num2 = stack01.pop();
numEnd = countNum(num1, num2, p1);
stack01.push(numEnd);
}
System.out.println("计算结果值为: "+stack01.pop());
}
public static void main(String[] args) {
countMachine("6*2+6-24+3");
countMachine("1+3+5+9-9*2");
countMachine("2+6*3/3+6+2");
countMachine("2+6-8+9*2/2-6");
}
}
表达式1:“6*2+6-24+3”
表达式2:“1+3+5+9-9*2”
表达式3:“2+6*3/3+6+2”
表达式4:“2+6-8+9*2/2-6”