一、栈的一个实际需求
请输入一个表达式
计算式:[7*2*2-5+1-5+3-3] 点击计算【如下图】
请问:计算机底层是如何运算得到结果的? 注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5,但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题。-> 栈
二、栈的介绍
1、栈的英文为(stack)
2、栈是一个先入后出(FILO-First In Last Out)的有序列表。
3、栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
4、根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
5、出栈(pop)和入栈(push)的概念(如图所示)
三、栈的应用场景
1、子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2、处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3、表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
3、二叉树的遍历。
4、图形的深度优先(depth一first)搜索法。
四、栈的快速入门
由于栈是一种有序列表,当然可以使用数组、链表...结构来储存栈的数据内容
4.1、用数组模拟栈的使用
4.1.1、思路分析
1、使用数组来模拟栈
2、定义一个 top 来表示栈顶,初始化 为 -1
3、入栈的操作,当有数据加入到栈时, top++; stack[top] = value;
4、出栈的操作, int value = stack[top]; top--;return value
4.1.2、代码实现
public class StackUsingArrayDemo {
public static void main(String[] args) {
StackUsingArray stack = new StackUsingArray(5); // 初始化容量为5的栈
// 压栈
stack.push(10);
stack.push(20);
stack.push(30);
// 打印栈内元素
stack.printStack(); // 输出: 30 20 10
// 弹栈
System.out.println("弹出元素: " + stack.pop()); // 输出: 30
// 查看栈顶元素
System.out.println("栈顶元素: " + stack.peek()); // 输出: 20
// 打印栈内元素
stack.printStack(); // 输出: 20 10
}
}
// 使用数组实现栈
class StackUsingArray {
private int[] stack;
private int top;
private int capacity;
// 构造函数,初始化栈的容量
public StackUsingArray(int capacity) {
this.capacity = capacity;
stack = new int[capacity];
top = -1; // 栈顶初始化为-1,表示栈为空
}
// 压栈操作
public void push(int value) {
if (isFull()) {
throw new RuntimeException("栈满,无法压入元素");
}
stack[++top] = value; // 栈顶指针加1后,将值压入栈顶
}
// 弹栈操作
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空,无法弹出元素");
}
return stack[top--]; // 返回栈顶元素,栈顶指针减1
}
// 查看栈顶元素
public int peek() {
if (isEmpty()) {
throw new RuntimeException("栈空,没有元素可以查看");
}
return stack[top];
}
// 判断栈是否为空
public boolean isEmpty() {
return top == -1;
}
// 判断栈是否已满
public boolean isFull() {
return top == capacity - 1;
}
// 打印栈内元素
public void printStack() {
if (isEmpty()) {
System.out.println("栈空");
} else {
for (int i = top; i >= 0; i--) {
System.out.print(stack[i] + " ");
}
System.out.println();
}
}
}
4.2、用链表模拟栈
4.2.1、思路分析
1、使用链表来模拟栈
2、定义一个 top 来表示栈顶,初始化 为 null
3、入栈的操作,当有数据加入到栈时, newNode.next = top;top = newNode;
4、出栈的操作,int value = top.value;top = top.next;return value;
4.2.2、代码实现
public class StackUsingLinkedListDemo {
public static void main(String[] args) {
StackUsingLinkedList stack = new StackUsingLinkedList();
// 压栈
stack.push(10);
stack.push(20);
stack.push(30);
// 打印栈内元素
stack.printStack(); // 输出: 30 20 10
// 弹栈
System.out.println("弹出元素: " + stack.pop()); // 输出: 30
// 查看栈顶元素
System.out.println("栈顶元素: " + stack.peek()); // 输出: 20
// 打印栈内元素
stack.printStack(); // 输出: 20 10
}
}
// 定义链表节点
class ListNode {
int value;
ListNode next;
// 构造函数
ListNode(int value) {
this.value = value;
this.next = null;
}
}
// 定义栈
class StackUsingLinkedList {
private ListNode top;
// 构造函数
public StackUsingLinkedList() {
this.top = null;
}
// 压栈操作
public void push(int value) {
ListNode newNode = new ListNode(value);
if (top == null) {
top = newNode;
} else {
newNode.next = top;
top = newNode;
}
}
// 弹栈操作
public int pop() {
if (top == null) {
throw new RuntimeException("栈空,无法弹出元素");
}
int value = top.value;
top = top.next;
return value;
}
// 查看栈顶元素
public int peek() {
if (top == null) {
throw new RuntimeException("栈空,没有元素可以查看");
}
return top.value;
}
// 判断栈是否为空
public boolean isEmpty() {
return top == null;
}
// 打印栈内元素
public void printStack() {
ListNode current = top;
while (current != null) {
System.out.print(current.value + " ");
current = current.next;
}
System.out.println();
}
}
五、栈实现综合计算器(中缀表达式的计算)
5.1、思路分析
1、初始化两个栈,操作数栈和运算符栈
2、若扫描到操作数,压入操作数栈
3、若扫描到运算符或界限符,分如下情况
3.1)当前的符号栈为空,就直接入栈
3.2)符号栈有操作符,就进行比较
3.2.1)当前的操作符的优先级<=栈中的操作符, 就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈
3.2.2)当前的操作符的优先级大于栈中的操作符, 就直接入符号栈
4、当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行.
5、最后在数栈只有一个数字,就是表达式的结果
5.2、代码实现
import java.util.Scanner;
import java.util.Stack;
public class InfixNotionCalculator {
// 判断是否为操作符
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
// 获取操作符的优先级
private int getPriority(char c) {
if (c == '+' || c == '-') {
return 1;
} else if (c == '*' || c == '/') {
return 2;
}
return 0; // 对于非操作符,优先级为0
}
// 进行计算
private double calculate(double a, double b, char operator) {
switch (operator) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
return a / b;
default:
return 0;
}
}
// 计算中缀表达式的值
public double evaluate(String expression) {
Stack<Double> numbers = new Stack<>();
Stack<Character> operators = new Stack<>();
int i = 0;
while (i < expression.length()) {
char c = expression.charAt(i);
// 如果是空格,则跳过
if (c == ' ') {
i++;
continue;
}
// 如果是数字或小数点
if (Character.isDigit(c) || c == '.') {
StringBuilder numStr = new StringBuilder();
// 处理整数和小数
while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
numStr.append(expression.charAt(i));
i++;
}
numbers.push(Double.parseDouble(numStr.toString()));
continue; // i已经在内部处理过了,直接跳到下一次循环
}
// 如果是左括号,直接压入操作符栈
if (c == '(') {
operators.push(c);
}
// 如果是右括号,则计算到左括号为止
else if (c == ')') {
while (operators.peek() != '(') {
numbers.push(calculate(numbers.pop(), numbers.pop(), operators.pop()));
}
operators.pop(); // 弹出左括号
}
// 如果是操作符
else if (isOperator(c)) {
while (!operators.isEmpty() && getPriority(operators.peek()) >= getPriority(c)) {
numbers.push(calculate(numbers.pop(), numbers.pop(), operators.pop()));
}
operators.push(c);
}
i++;
}
// 处理剩余的操作符
while (!operators.isEmpty()) {
numbers.push(calculate(numbers.pop(), numbers.pop(), operators.pop()));
}
return numbers.pop();
}
// 测试
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
InfixNotionCalculator calculator = new InfixNotionCalculator();
System.out.println("请输入后缀表达式(以空格分隔):");
String expression = scanner.next();//3.5 + 5 * ( 2.2 - 8.1 )
System.out.println("表达式: " + expression);
System.out.println("结果: " + calculator.evaluate(expression));
}
}
六、前缀、中缀、后缀表达式
6.1、前缀表达式(波兰表达式)
6.1.1、基本介绍
1、前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
2、举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6
6.1.2、前缀表达式的计算机求值
6.1.2.1、说明
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
6.1.2.2、举例
(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
1)从右至左扫描,将6、5、4、3压入堆栈
2)遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
3)接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
4)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
6.1.2.3、代码实现
import java.util.Scanner;
import java.util.Stack;
public class PrefixCalculator {
// 判断是否为操作符
private boolean isOperator(String token) {
return token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/");
}
// 进行计算
private double calculate(double a, double b, String operator) {
switch (operator) {
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
return a / b;
default:
throw new IllegalArgumentException("无效的操作符: " + operator);
}
}
// 计算前缀表达式的值
public double evaluate(String[] expression) {
Stack<Double> stack = new Stack<>();
// 从右向左遍历前缀表达式
for (int i = expression.length - 1; i >= 0; i--) {
String token = expression[i];
if (isOperator(token)) {
// 弹出两个操作数
double a = stack.pop();
double b = stack.pop();
// 计算并将结果压入栈中
stack.push(calculate(a, b, token));
} else {
// 将操作数压入栈中
stack.push(Double.parseDouble(token));
}
}
// 最终栈中应只剩下一个值,即为表达式的结果
return stack.pop();
}
// 测试
public static void main(String[] args) {
PrefixCalculator calculator = new PrefixCalculator();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入前缀表达式(以空格分隔):");
String input = scanner.nextLine();
String[] expression = input.split(" "); // 根据空格分割输入的表达式
try {
double result = calculator.evaluate(expression);
System.out.println("结果: " + result);
} catch (Exception e) {
System.out.println("表达式错误: " + e.getMessage());
} finally {
scanner.close();
}
}
}
6.2、中缀表达式
1、中缀表达式就是常见的运算表达式,如(3+4)×5-6
2、中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作,因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)
3、代码实现见前面的“五、栈实现综合计算器”
6.3、后缀表达式(逆波兰表达式)
6.3.1、基本介绍
1、后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
2、举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6-
再比如:
正常的表达式 | 逆波兰表达式 |
a+b | a b + |
a+(b-c) | a b c - + |
a+(b-c)*d | a b c – d * + |
a+d*(b-c) | a d b c - * + |
a=1+3 | a 1 3 + = |
6.3.2、后缀表达式的计算机求值
6.3.2.1、说明
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
6.3.2.2、举例
(3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
1)从左至右扫描,将3和4压入堆栈;
2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3)将5入栈;
4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5)将6入栈;
6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
6.3.2.3、代码实现(逆波兰计算器)
输入一个逆波兰表达式(后缀表达式),使用栈(Stack), 计算其结果支持小括号和多位数小数
6.3.2.3.1、思路分析
遍历后缀表达式的每个元素:
如果是数字,解析为double并压入栈中。
如果是操作符,弹出栈顶的两个数字,进行相应的运算,将结果压入栈中。
表达式遍历完成后,栈中剩余的唯一数字即为最终结果。
6.3.2.3.2、代码完成
import java.util.Scanner;
import java.util.Stack;
public class RPN_Calculator {
// 判断是否为操作符
private boolean isOperator(String token) {
return token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/");
}
// 进行计算
private double calculate(double a, double b, String operator) {
switch (operator) {
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
return a / b;
default:
throw new IllegalArgumentException("无效的操作符: " + operator);
}
}
// 计算后缀表达式的值
public double evaluate(String[] expression) {
Stack<Double> stack = new Stack<>();
for (String token : expression) {
if (isOperator(token)) {
// 弹出两个操作数
double b = stack.pop();
double a = stack.pop();
// 计算并将结果压入栈中
stack.push(calculate(a, b, token));
} else {
// 将操作数压入栈中
stack.push(Double.parseDouble(token));
}
}
// 最终栈中应只剩下一个值,即为表达式的结果
return stack.pop();
}
// 测试
public static void main(String[] args) {
RPN_Calculator calculator = new RPN_Calculator();
// 示例后缀表达式: "3.5 5 2.2 8.1 - * +"
String[] expression = {"3.5", "5", "2.2", "8.1", "-", "*", "+"};
System.out.println("表达式: 3.5 5 2.2 8.1 - * +");
System.out.println("结果: " + calculator.evaluate(expression));
/** 测试数据手动输入
* Scanner scanner = new Scanner(System.in);
* RPNCalculator calculator = new RPNCalculator();
* System.out.println("请输入后缀表达式(以空格分隔):");
* String input = scanner.nextLine();
* String[] expression = input.split(" "); // 根据空格分割输入的表达式
* try {
* double result = calculator.evaluate(expression);
* System.out.println("结果: " + result);
* } catch (Exception e) {
* System.out.println("表达式错误: " + e.getMessage());
* } finally {
* scanner.close();
* }
*/
}
}
6.4、中缀表达式-->后缀表达式
6.4.1、说明
后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,需要将中缀表达式转成后缀表达式。
6.4.2、思路分析
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。从左到右处理各个元素,直到末尾。可能遇到三种情况:
①遇到操作数。直接加入后缀表达式。
② 遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
③遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到“(”或栈空则停止。之后再把当前运算符入栈。
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
注:结果的逆序即为中缀表达式对应的后缀表达式
6.4.3、代码实现
import java.util.Stack;
import java.util.ArrayList;
import java.util.List;
public class InfixToRPNConverter {
// 判断是否为操作符
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
// 获取操作符的优先级
private int getPriority(char c) {
switch (c) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
// 判断是否为左括号
private boolean isLeftParenthesis(char c) {
return c == '(';
}
// 判断是否为右括号
private boolean isRightParenthesis(char c) {
return c == ')';
}
// 将中缀表达式转换为后缀表达式
public String[] infixToPostfix(String expression) {
Stack<Character> operatorStack = new Stack<>();
List<String> postfixList = new ArrayList<>();
int i = 0;
while (i < expression.length()) {
char c = expression.charAt(i);
// 如果是空格,则跳过
if (c == ' ') {
i++;
continue;
}
// 如果是数字或小数点,处理数字
if (Character.isDigit(c) || c == '.') {
StringBuilder number = new StringBuilder();
while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
number.append(expression.charAt(i));
i++;
}
postfixList.add(number.toString());
continue; // 跳到下一个字符
}
// 如果是左括号,直接压入栈中
if (isLeftParenthesis(c)) {
operatorStack.push(c);
}
// 如果是右括号,弹出栈中所有操作符,直到遇到左括号
else if (isRightParenthesis(c)) {
while (!operatorStack.isEmpty() && !isLeftParenthesis(operatorStack.peek())) {
postfixList.add(String.valueOf(operatorStack.pop()));
}
operatorStack.pop(); // 弹出左括号
}
// 如果是操作符
else if (isOperator(c)) {
while (!operatorStack.isEmpty() && getPriority(operatorStack.peek()) >= getPriority(c)) {
postfixList.add(String.valueOf(operatorStack.pop()));
}
operatorStack.push(c);
}
i++;
}
// 弹出栈中剩余的操作符
while (!operatorStack.isEmpty()) {
postfixList.add(String.valueOf(operatorStack.pop()));
}
// 将后缀表达式转换为字符串数组并返回
return postfixList.toArray(new String[0]);
}
// 测试
public static void main(String[] args) {
InfixToRPNConverter converter = new InfixToRPNConverter();
String expression = "3.5 + 5 * ( 2.2 - 8.1 )";
System.out.println("中缀表达式: " + expression);
String[] postfix = converter.infixToPostfix(expression);
System.out.println("后缀表达式: " + String.join(" ", postfix));
}
}