一、栈的概述
1.1 什么是栈
栈(stack)是一种基于先进后出(FILO-First In Last Out)策略的集合类型。栈是一种特殊的线性表,它限制元素的插入和删除只能在同一端进行。允许插入和删除元素的一端,为变化的一端,称为栈顶;另一端为固定的一端,称为栈底。
根据栈的定义可以知道:最先放入的元素在栈底,最后放入的元素在栈顶;而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
下图形象地描述了元素出栈和入栈的过程:
|
|
1.2 栈的应用场景
栈作为一种重要的基本数据结构,它的应用是比较广泛的。栈的应用包括如下几个方面:
- 子程序的调用:在跳往子程序前, 会先将下个指令的地址存到堆栈中, 直到子程序执行完后再将地址取出, 以
回到原来的程序中; - 处理递归调用:和子程序的调用类似, 只是除了储存下一个指令的地址外, 也将参数、 区域变量等数据存入堆
栈中; - 表达式的转换(中缀表达式转后缀表达式)与求值;
- 二叉树的遍历;
- 图形的深度优先(depth 一 first)搜索法。
二、用数组模拟栈
由于栈是一种有序列表, 当然可以使用数组的结构来储存栈的数据内容。下面我们就用数组模拟栈的出栈, 入栈等操作。
2.1 思路分析
使用数组来模拟栈的思路是比较简单的,按照下面的步骤即可:
- 定义一个类,该类的成员变量包括一个数组
stack
(用于模拟栈)、两个整型变量maxSize
、top
(分别代表栈的大小、栈顶指针); - 栈顶指针
top
初始化为 -1; - 每当有元素要入栈时,
top
加 1,然后元素记录在数组中,即stack[top] = element
; - 每当有元素要出栈时,先读取数组的元素,即
element = stack[top]
,然后top
减 1。
2.2 代码实现
public class No1_StackBasicDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(10);
Scanner input = new Scanner(System.in);
boolean flag = true;
while (flag){
System.out.println("-------M E N U -------");
System.out.println("请选择您的操作:");
System.out.println("push:\t数据入栈");
System.out.println("pop:\t数据出栈");
System.out.println("show:\t打印栈数据");
System.out.println("exit:\t退出程序");
System.out.println("-------M E N U -------");
String select = input.nextLine();
switch (select){
case "push": // 入栈
System.out.println("请输入要入栈的元素:");
int num = Integer.parseInt(input.nextLine());
stack.push(num);
// 就是为了让程序停一下
System.out.print("按下任意键继续:");
input.nextLine();
break;
case "pop": // 出栈
try{
int pop = stack.pop();
System.out.println("出栈的元素为:" + pop);
} catch (RuntimeException e){
System.out.println(e.getMessage());
}
// 就是为了让程序停一下
System.out.print("按下任意键继续:");
input.nextLine();
break;
case "show": // 打印数据
stack.showStack();
// 程序停一下
System.out.print("按下任意键继续:");
input.nextLine();
break;
case "exit": // 退出程序
flag = false;
break;
default:
break;
}
}
}
}
/**
* @Description 使用数组模拟栈
*/
class ArrayStack{
private int maxSize; // 栈的大小
private int[] array; // 此数组用于模拟栈
private int pos; // 栈的指针
public ArrayStack(int maxSize){
this.maxSize = maxSize;
array = new int[maxSize];
pos = -1; // 指针初始指向第 -1 个位置
}
// 判断栈是否满了
public boolean isFull(){
return pos == maxSize - 1;
}
// 判断栈是否为空
public boolean isEmpty(){
return pos == -1;
}
// 打印栈中数据
public void showStack(){
if (isEmpty()){
System.out.println("栈为空!");
return;
}
System.out.println("============================");
System.out.println("栈中内容如下:");
System.out.println("---------");
for (int i=pos; i>=0; i--){
System.out.println("|\t" + array[i] + "\t|") ;
System.out.println("---------");
}
}
// 模拟入栈
public void push(int num){
// 1. 首先判断栈是否满了
if (isFull()){
System.out.println("栈满了,无法入栈!");
return;
}
// 2. 先把指针加 1,然后入栈
array[++pos] = num;
System.out.println(num + " 入栈成功!");
}
// 模拟出栈
public int pop(){
// 1. 首先判断是否为空
if (isEmpty()){
throw new RuntimeException("栈空了,无法出栈!");
}
// 2. 先让数据出栈,然后指针减 1
return array[pos--];
}
}
三、栈实现简单计算器(中缀)
使用栈来实现一个简单的计算器,该计算器包括最基本的计算功能:加、减、乘、除。
例:
输入:3+2*6-1
输出:14
3.1 思路分析
表达式分为中缀表达式、前缀表达式、后缀表达式。中缀表达式就是表达式本身,如 “3+2*6-1” 就是一个中缀表达式。关于表达式的详细介绍将会在后面的博客中展开说明。
本案例实现的简单计算器就是直接对中缀表达式(也就是原计算表达式)进行分析处理。
如果要实现一个计算器,可以按照以下思路:
-
初始化两个栈,一个作为符号栈、一个作为数字栈;
-
通过一个索引
index
,来从左至右遍历中缀表达式; -
如果遍历到的是一个数字,就直接入数字栈;
-
如果遍历到的是一个符号:
-
如果当前符号栈为空,就直接入符号栈;
-
如果符号栈有操作符,就进行比较:
- 若当前的操作符优先级小于或等于栈顶的操作符,就从数字栈中
pop
出两个数,再从符号栈中pop
出一个符号进行运算。运算得到的结果push
入数字栈中,然后将当前的操作符入符号栈; - 若当前的操作符优先级大于栈顶的操作符,就直接入符号栈;
- 若当前的操作符优先级小于或等于栈顶的操作符,就从数字栈中
-
-
中缀表达式遍历完毕之后,就依次从数字栈和符号栈中
pop
出相应的数和符号,对他们进行运算; -
最后在数字栈中将只剩下一个数字,这个数字就是表达式的结果。
3.2 代码实现
Java 提供了栈类 Stack
,本例中依然使用的是数组来模拟栈,接下来的博客中将直接使用 Java 提供的 Stack
类。
栈实现中缀表达式的简单计算器的源码如下:
public class No2_Stack_BasicCalculator {
public static void main(String[] args) {
String str = "300+20*6-10*1";
ArrayNumStack numStack = new ArrayNumStack(5);
ArrayOperStack operStack = new ArrayOperStack(5);
int num = 0;
// 具体计算
int length = str.length();
for (int index = 0; index < length; index++) {
char param = str.charAt(index);
// 判断是不是数字
if ('0' <= param && param <= '9') {
// 如果是数字
num = num * 10 + param - 48;
// 还需要往后判断一位,看看是数字还是符号,如果是符号就进栈,如果是数字就不急着进栈
// 但是,需要首先看看后面还有没有数据了
if (index + 1 < length) {
// 如果后面还有数据,就取出来看看是符号还是数字
char next = str.charAt(index + 1);
if (!(next >= '0' && next <= '9')) {
numStack.pushNum(num);
num = 0; // 初始化
}
} else {
// 如果后面没数据了,说明到了字符串最后了
numStack.pushNum(num);
}
} else {
// 如果不是数字,就是符号
// 如果符号栈为空,直接进栈
if (operStack.isEmpty()) {
operStack.pushOper(param);
} else {
// 符号优先级比较了,用于判断是否做运算
// 先判断将要进栈的符号的优先级和栈顶的符号优先级哪个高
int pri_param = getPriority(param);
int pri_top = getPriority(operStack.peek());
if (pri_param >= pri_top) {
// 如果要进栈的符号优先级大于等于栈顶的,则直接进栈
operStack.pushOper(param);
} else {
// 如果要进栈的符号优先级小于等于栈顶的,那么栈顶元素出栈进行运算
char oper = operStack.popOper(); // 符号栈出一个元素
int num_1 = numStack.popNum(); // 数字栈出两个元素
int num_2 = numStack.popNum();
int res = calculate(num_1, num_2, oper); // 运算
numStack.pushNum(res); // 运算结果进栈
operStack.pushOper(param); // 运算符进栈
}
}
}
}
// 经过上面的操作,符号栈剩下的都是优先级相等的符号了,直接出栈做运算就可以啦
// 如果符号栈为空,说明已经计算完了
while (!operStack.isEmpty()) {
char oper = operStack.popOper();
int num_1 = numStack.popNum();
int num_2 = numStack.popNum();
int res = calculate(num_1, num_2, oper);
numStack.pushNum(res);
}
// 这个时候,数字栈的最后一个元素,就是最后的计算结果
System.out.println(str + " = " + numStack.popNum());
}
// 获取运算符优先级,数字越大,优先级越大
private static int getPriority(char oper) {
if (oper == '+' || oper == '-') {
return 0;
}
if (oper == '*' || oper == '/') {
return 1;
}
return -1;
}
// 给定两个数字以及运算符,计算出结果
private static int calculate(int num_1, int num_2, char oper) {
int result = 0;
switch (oper) {
case '+':
result = num_1 + num_2;
break;
case '-':
result = num_2 - num_1;
break;
case '*':
result = num_1 * num_2;
break;
case '/':
result = num_2 / num_1;
default:
break;
}
return result;
}
}
/**
* @Description 使用数组模拟数字栈
*/
class ArrayNumStack {
private int maxSize; // 栈的容量
private int[] numStack; // 存放数字的栈
private int top; // 数字栈的栈顶
public ArrayNumStack(int maxSize) {
this.maxSize = maxSize;
numStack = new int[maxSize];
top = -1;
}
// 数字入栈
public void pushNum(int num) {
if (isFull()) {
System.out.println("数字栈满了!");
return;
}
numStack[++top] = num;
}
// 数字出栈
public int popNum() {
if (isEmpty()) {
throw new RuntimeException("数字栈为空!");
}
return numStack[top--];
}
// 判断数字栈是否满了
private boolean isFull() {
return top == maxSize - 1;
}
// 判断数字栈是否为空
private boolean isEmpty() {
return top == -1;
}
}
/**
* @Description 数组模拟运算符栈
*/
class ArrayOperStack {
private int maxSize; // 运算符栈的大小
private char[] operStack; // 模拟运算符栈
private int top; // 栈顶指针
public ArrayOperStack(int maxSize) {
this.maxSize = maxSize;
operStack = new char[maxSize];
top = -1;
}
// 看一下运算符栈栈顶元素是什么,用于比较运算符优先级
public char peek() {
return operStack[top];
}
// 运算符入栈
public void pushOper(char val) {
if (isFull()) {
System.out.println("运算符栈满了!");
return;
}
operStack[++top] = val;
}
// 运算符出栈
public char popOper() {
if (isEmpty()) {
throw new RuntimeException("运算符栈为空!");
}
return operStack[top--];
}
// 判断运算符栈是否满了
private boolean isFull() {
return top == maxSize - 1;
}
// 判断运算符栈是否为空
public boolean isEmpty() {
return top == -1;
}
}