Stack类:常见方法讲解、使用场景、底层实现及算法问题

Stack 类是 Java 集合框架中的一个经典类,用于实现后进先出(LIFO, Last In First Out)数据结构。虽然 Stack 类作为一种直接的堆栈实现存在,但在开发中,DequeLinkedList 更常被推荐用于堆栈的实现。不过,Stack 类仍然是一个重要的基础类,特别是在教学和理解算法设计时。
本文将通过深入分析 Stack 的常见方法、使用场景、底层数据结构实现逻辑,并结合常见的算法问题进行讲解。


1. 什么是 Stack 类?

Stack 类是一种继承自 Vector 的数据结构,提供了典型的堆栈操作,如 pushpoppeekemptysearch。作为 LIFO 的代表,Stack 主要用于处理需要后进先出的场景。

public class Stack<E> extends Vector<E> {public Stack() {}public E push(E item) {addElement(item);return item;}public synchronized E pop() {E obj;int len = size();obj = peek();removeElementAt(len - 1);return obj;}public synchronized E peek() {int len = size();if (len == 0)throw new EmptyStackException();return elementAt(len - 1);}public boolean empty() {return size() == 0;}public synchronized int search(Object o) {int i = lastIndexOf(o);if (i >= 0) {return size() - i;}return -1;}
}

上面的代码展示了 Stack 类的基本实现,主要是通过继承 Vector 类来管理存储的数据。


2. 常见方法讲解

2.1 push(E item)

将元素 item 压入堆栈顶部。

底层实现: Stack 类继承了 Vector,而 Vector 本质上是一个动态数组,push 方法调用 VectoraddElement() 方法,将新元素追加到数组末尾。

示例:

Stack<Integer> stack = new Stack<>();
stack.push(10);
stack.push(20);
stack.push(30);

在这个示例中,stack 存储了 10, 20 和 30,30 位于栈顶。

2.2 pop()

移除并返回堆栈顶部的元素。如果堆栈为空,则抛出 EmptyStackException

底层实现: pop() 方法首先调用 peek() 获取栈顶元素,然后移除最后一个元素,使用 removeElementAt()

示例:

int top = stack.pop(); // 返回并移除30

2.3 peek()

仅返回栈顶元素,但不移除。

底层实现: peek() 直接返回 Vector 的最后一个元素。

示例:

int top = stack.peek(); // 返回30,但不移除

2.4 empty()

判断堆栈是否为空,若为空则返回 true,否则返回 false

2.5 search(Object o)

查找对象 o 在栈中的位置,位置是以 1 为基准的,栈顶元素位置为 1。如果未找到,返回 -1

示例:

int pos = stack.search(20); // 返回2,因为20在栈顶的下面

3. 类图

在这里插入图片描述

4. 底层数据结构实现逻辑

Stack 类继承了 Vector,因此它的底层数据结构是一个动态数组。当数组容量不足时,Vector 会自动扩展容量,具体的扩展方式是每次扩展原容量的两倍。这样的设计虽然简单,但在并发场景下,由于 Vector 的所有方法都带有 synchronized,性能会受到一定影响。因此,在多线程环境中使用 Stack 类可能不是最佳选择,推荐使用 ConcurrentLinkedDequeBlockingQueue


5. 常见 Stack 相关的算法问题

5.1 括号匹配问题

问题描述: 给定一个包含括号的字符串,判断括号的匹配是否合法。

示例: 输入:"({[]})"
输出:true

解决方式: 使用堆栈存储左括号,当遇到右括号时,弹出栈顶元素并检查是否匹配。

public boolean isValid(String s) {Stack<Character> stack = new Stack<>();for (char c : s.toCharArray()) {if (c == '(' || c == '{' || c == '[') {stack.push(c);} else {if (stack.empty()) return false;char top = stack.pop();if ((c == ')' && top != '(') || (c == '}' && top != '{') || (c == ']' && top != '[')) {return false;}}}return stack.empty();
}

5.2 栈排序问题

问题描述: 给定一个栈,将栈中的元素按升序排序,只能使用一个额外的栈作为辅助存储。

解决方式: 利用两个栈,一个栈负责存储排序后的元素,另一个栈用于暂时存放未排序的元素。

public Stack<Integer> sortStack(Stack<Integer> stack) {Stack<Integer> sortedStack = new Stack<>();while (!stack.empty()) {int temp = stack.pop();while (!sortedStack.empty() && sortedStack.peek() > temp) {stack.push(sortedStack.pop());}sortedStack.push(temp);}return sortedStack;
}

5.3 回溯算法

问题描述: 回溯算法是一种用于解决需要搜索所有可能解决方案的问题的算法技术,尤其是在遇到迷宫、数独、八皇后等问题时常常使用。回溯算法的本质是通过不断地尝试和撤销选择,以找到问题的解决方案。在这个过程中,堆栈是一种理想的结构来存储状态信息,以便于恢复之前的状态。

在电商交易系统中,回溯算法可以用于解决复杂的订单路径规划问题,例如计算多个配送员的配送路径、根据优先级和时间窗口调整订单的最佳配送路径等。

示例:迷宫求解问题

假设有一个二维迷宫,我们需要找到从起点 (0, 0) 到终点的路径。迷宫中可能有障碍物,机器人只能上下左右移动。

class Position {int x, y;public Position(int x, int y) {this.x = x;this.y = y;}
}public class MazeSolver {private static final int[][] DIRECTIONS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};private int[][] maze;private boolean[][] visited;public MazeSolver(int[][] maze) {this.maze = maze;this.visited = new boolean[maze.length][maze[0].length];}public boolean solveMaze() {Stack<Position> path = new Stack<>();path.push(new Position(0, 0));while (!path.empty()) {Position current = path.pop();// 判断是否到达终点if (isGoal(current)) {System.out.println("找到了解决路径!");return true;}// 标记当前点为已访问visited[current.x][current.y] = true;// 遍历四个方向,查找未访问的相邻点for (int[] direction : DIRECTIONS) {int newX = current.x + direction[0];int newY = current.y + direction[1];if (isValid(newX, newY)) {path.push(new Position(newX, newY));}}}System.out.println("没有找到路径!");return false;}private boolean isGoal(Position position) {return position.x == maze.length - 1 && position.y == maze[0].length - 1;}private boolean isValid(int x, int y) {return x >= 0 && x < maze.length && y >= 0 && y < maze[0].length && maze[x][y] == 0 && !visited[x][y];}public static void main(String[] args) {int[][] maze = {{0, 1, 0, 0, 0},{0, 1, 0, 1, 0},{0, 0, 0, 1, 0},{1, 1, 1, 1, 0},{0, 0, 0, 0, 0}};MazeSolver solver = new MazeSolver(maze);solver.solveMaze();}
}

解决方式:

  • 堆栈的使用: 每次移动时,将当前位置压入堆栈,当找到一个死路时,通过 pop() 操作回溯到上一个位置。
  • 时间复杂度: 由于每个位置最多访问一次,时间复杂度为 O(n),其中 n 是迷宫格子的数量。
  • 适用场景: 在电商系统中,该方法可以用于寻找订单路径规划中的最优路线。

5.4 表达式求解

问题描述: 表达式求解是计算机科学中的经典问题,尤其是中缀表达式转后缀表达式以及后缀表达式求值。这类问题常用于计算器程序、编译器等领域。

示例:中缀表达式转后缀表达式

中缀表达式是一种操作符位于操作数中间的表达方式,例如 2 + 3 * 4。为了简化计算,我们通常将中缀表达式转换为后缀表达式(如 2 3 4 * +)。后缀表达式没有运算符优先级问题,可以直接从左到右求值。

使用堆栈进行中缀表达式的转换是非常高效的,主要步骤如下:

  1. 遇到操作数时,直接输出。
  2. 遇到操作符时,比较它与栈顶操作符的优先级,若栈顶优先级较高或相等,则弹出栈顶操作符并输出,直到栈顶优先级较低或栈为空。
  3. 遇到左括号时,直接压栈。
  4. 遇到右括号时,弹出栈顶操作符,直到遇到左括号为止。

解决方式:

import java.util.Stack;public class InfixToPostfix {// 判断是否为操作符private static boolean isOperator(char ch) {return ch == '+' || ch == '-' || ch == '*' || ch == '/';}// 获取操作符的优先级private static int getPriority(char operator) {if (operator == '+' || operator == '-') {return 1;} else if (operator == '*' || operator == '/') {return 2;}return 0;}public static String infixToPostfix(String expression) {Stack<Character> stack = new Stack<>();StringBuilder result = new StringBuilder();for (char ch : expression.toCharArray()) {if (Character.isLetterOrDigit(ch)) {result.append(ch);} else if (ch == '(') {stack.push(ch);} else if (ch == ')') {while (!stack.isEmpty() && stack.peek() != '(') {result.append(stack.pop());}stack.pop(); // 弹出左括号} else if (isOperator(ch)) {while (!stack.isEmpty() && getPriority(stack.peek()) >= getPriority(ch)) {result.append(stack.pop());}stack.push(ch);}}// 弹出栈中剩余的操作符while (!stack.isEmpty()) {result.append(stack.pop());}return result.toString();}public static void main(String[] args) {String expression = "a+b*(c^d-e)^(f+g*h)-i";String postfix = infixToPostfix(expression);System.out.println("后缀表达式为: " + postfix);}
}

解决方式:

  • 堆栈的使用: 操作符依次压入堆栈,遇到右括号或优先级较低的操作符时,进行 pop() 操作。
  • 时间复杂度: O(n),其中 n 是表达式的长度。
  • 适用场景: 这种表达式求解方式可用于电商系统中的复杂规则计算,例如折扣规则计算、积分计算等。

5.5 矩阵旋转问题

问题描述: 给定一个 n x n 的矩阵,要求顺时针旋转 90 度。

示例: 输入:

csharp复制代码[[1, 2, 3],[4, 5, 6],[7, 8, 9]
]

输出:

csharp复制代码[[7, 4, 1],[8, 5, 2],[9, 6, 3]
]

解决方式: 该问题可以通过先转置矩阵,然后再翻转每一行来解决。虽然这个问题并不直接依赖堆栈,但通过借助堆栈的 LIFO 特性,也可以模拟旋转矩阵的过程。

public class RotateMatrix {public static void rotate(int[][] matrix) {int n = matrix.length;// 1. 先转置矩阵for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {int temp = matrix[i][j];matrix[i][j] = matrix[j][i];matrix[j][i] = temp;}}// 2. 翻转每一行for (int i = 0; i < n; i++) {for (int j = 0; j < n / 2; j++) {int temp = matrix[i][j];matrix[i][j] = matrix[i][n - 1 - j];matrix[i][n - 1 - j] = temp;}}}public static void main(String[] args) {int[][] matrix = {{1, 2, 3},{4, 5, 6},{7, 8, 9}};rotate(matrix);for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix[i].length; j++) {System.out.print(matrix[i][j] + " ");}System.out.println();}}
}

解决方式:

  • 步骤1 - 转置矩阵: 通过交换矩阵的行和列位置,将矩阵转置。对于 matrix[i][j]matrix[j][i] 进行交换操作,转置完成后矩阵的列变成了行。
  • 步骤2 - 翻转每一行: 对于转置后的矩阵,每一行的元素从左到右翻转,这一步骤完成后,矩阵即为顺时针旋转 90 度的结果。
  • 时间复杂度: O(n^2),其中 n 是矩阵的维度,因为每个元素都需要被访问一次。
  • 适用场景: 这种矩阵旋转方法在图像处理、游戏开发等领域中非常常见,能够高效处理二维数据的旋转。

5.6 栈的最小元素问题

问题描述: 设计一个栈,支持常数时间复杂度的获取栈中最小元素操作。

示例: 操作:

  1. push(1)
  2. push(2)
  3. push(-1)
  4. getMin() 返回 -1
  5. pop()
  6. getMin() 返回 1

解决方式:

要实现常数时间复杂度的获取栈中最小元素,我们可以使用两个栈:一个主栈和一个辅助栈。辅助栈始终保持当前主栈中的最小元素。

import java.util.Stack;public class MinStack {private Stack<Integer> stack;private Stack<Integer> minStack;public MinStack() {stack = new Stack<>();minStack = new Stack<>();}public void push(int x) {stack.push(x);if (minStack.isEmpty() || x <= minStack.peek()) {minStack.push(x);}}public void pop() {int popped = stack.pop();if (popped == minStack.peek()) {minStack.pop();}}public int top() {return stack.peek();}public int getMin() {return minStack.peek();}public static void main(String[] args) {MinStack minStack = new MinStack();minStack.push(1);minStack.push(2);minStack.push(-1);System.out.println(minStack.getMin()); // 输出 -1minStack.pop();System.out.println(minStack.getMin()); // 输出 1}
}

解决方式:

  • 主栈: 存储所有入栈的元素。
  • 辅助栈: 存储每个主栈状态下的最小元素。每次入栈时,如果当前元素小于等于辅助栈的栈顶元素,则将该元素也压入辅助栈;每次出栈时,如果出栈的元素等于辅助栈的栈顶元素,则辅助栈也出栈。
  • 时间复杂度: O(1),无论是 pushpop 还是 getMin 操作,时间复杂度均为常数时间。
  • 适用场景: 这种设计在需要频繁访问最小元素并且要求高效的场景非常有用,例如实时监控数据的最小值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/142824.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

为什么说Claude3.5 sonnet好于GPT4O?实为网友们的无耐选择

引言 写作时&#xff0c;选择合适的工具就像船长选择航行的船只。语言模型作为目前最流行的技术工具之一&#xff0c;涉及每个人的生活与工作。Claude和GPT-4o是两款备受关注的语言模型&#xff0c;许多人自然而然地将二者进行比较&#xff0c;认为Claude更优。然而&#xff0…

时间复杂度计算 递归(solve2 后续)

原帖 最近校内比较忙&#xff0c;更新缓慢&#xff0c;致歉。 这里函数每次都需要遍历 h h h 和 m m m 之间的数&#xff08;复杂度 O ( n ) O(n) O(n)&#xff09;&#xff0c;所以和 solve1 略有不同。仍然假设 T ⁡ ( n ) \operatorname{T}(n) T(n) 表示 m − h 1 n…

python五子棋之对战项目源码【免费】

五子棋之对战项目是一种基于五子棋游戏规则的在线或本地对战项目。五子棋作为一种两人对弈的纯策略型棋类游戏&#xff0c;具有简单易学、策略性强的特点&#xff0c;因此非常适合作为对战项目的核心玩法。这个项目源码是使用Python编程语言实现的 源码下载地址&#xff1a; …

STL相关简介

string 看到这个词&#xff0c;相信大家一定都很好奇什么是string&#xff0c;它有什么作用呢&#xff1f;今天&#xff0c;就让我们一起来了解一下关于string的简介吧~ 目录 string 1. 什么是STL 2. STL的版本 3. STL的六大组件 4. STL的重要性 5. 如何学习STL 6.STL的…

【3D打印】使用simplify 3D切片更改Gcode手动断电续打、掉电、未打完继续打印、补救

一、问题描述 有些时候会遇到3D打印机没料但机器还在继续打、掉电重启后未正常恢复打印、挤出机端没有料但断料检测未触发等情况。我们又不想打印放弃&#xff0c;但又想继续之前的进度打印。 这时候我们需要更改3D打印文件的切片参数来进行继续打印。 图中问题&#xff1a;可…

知识图谱与异构图神经网络(7)--1

知识图谱是由实体(节点)和关系( 不同类型的边) 组成的多关系图。作为一种非常重要又特殊的图结构数据&#xff0c;知识图谱被广泛应用在人工智能和自然语言处理领域&#xff0c;从语义解析、命名实体消歧到问答系统、推荐系统中都可以看到来自知识图谱的技术推动。本质上&#…

微服务下设计一个注解标识是否需要登录

需求 现在我们是微服务系统&#xff0c;需要设计一个注解 RequiredLogin &#xff0c;当标识这个注解时表示系统需要登录才能继续操作。 实现思路 首先&#xff0c;需要明确我们要拦截的是从浏览器过来的请求&#xff0c;服务之间的互相调用是不需要拦截的&#xff08;比如 …

【python设计模式1】面向对象设计原则

目录 设计模式分类 面向对象 接口 面向对象设计原则 里氏替换原则 依赖倒置原则 接口隔离原则 单一职责原则 设计模式是对软件设计中普遍存在或反复出向的各种问题所提出的解决方案。每一个设计模式系统地被命名、解释和评价了面向对象系统中一个重要和重复出现的设计。…

基于MicroPython的ESP32控制LED灯闪烁设计方案的Wokwi仿真

以下是一个基于MicroPython的ESP32控制LED灯闪烁设计方案的Wokwi仿真&#xff1a; 一、硬件准备&#xff1a; 在Wokwi仿真平台(https://wokwi.com/)选择ESP32开发板&#xff0c;添加一个LED灯&#xff0c;和一个220欧姆限流电阻。 二、硬件连接&#xff1a; 1. 将LED灯的阳极…

【例题】lanqiao4403 希尔排序模板题

插入排序每次只能将数据移动一位。 已知插入排序代码为&#xff1a; def insert_sort(a):for i in range(1,len(a)):ji-1while j>0 and a[j]>a[i]:a[j1]a[j]j-1a[j1]a[i]return a希尔排序在插入排序的基础上&#xff0c;将数据移动n/2,n/4,…,1位。 for i in range(ga…

Git:Git管理

目录 Git 文件管理检测文件状态 status跟踪新文件 add提交更新 commit撤销提交 Commit Git 校验和历史查看 log版本回退 resetgit 忽略文件 Git 分支管理Git 提交对象Git master分支Git 分支管理本地分支管理远程分支管理分支hotfix处理 Git 工作流常见分支冲突处理分支合并冲突…

冒泡排序的C++语言实现(不用std::sort)

自己写一个冒泡排序的代码。 void vSort(std::vector<int> & vec, bool bDescending) {//冒泡排序int iTail vec.size()-1;while(iTail > 0){for(int k 0; k < iTail; k){int f1 vec.at(k);int f2 vec.at(k1);if(f1 < f2){//默认是降序int iTmp vec.a…

第十一章 【后端】商品分类管理微服务(11.3)——商品管理模块 yumi-etms-goods

11.3 商品管理模块 yumi-etms-goods 新建 yumi-etms-goods 模块 添加依赖 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns&#

ESP32工程添加.c .h文件及常见错误

一、编译环境添加 如果编译的文件在main文件夹下&#xff0c;在main文件夹下的CMakeLists.txt中添加对应的.c文件&#xff0c;如下图所示。 二、常见问题 1- undefined reference to xxx C语言中使用static修饰函数时&#xff0c;意味着该函数的作用域仅限于定义它的文件。…

【Python123题库】#体育收入排行2012-2019

禁止转载&#xff0c;原文&#xff1a;https://blog.csdn.net/qq_45801887/article/details/140087809 参考教程&#xff1a;B站视频讲解——https://space.bilibili.com/3546616042621301 有帮助麻烦点个赞 ~ ~ Python123题库 体育收入排行2012-2019 体育收入排行2012-2019 …

CentOS7 MySQL8.0 启动失败 Data Dictionary initialization failed

CentOS7 MySQL8.0 启动失败 Data Dictionary initialization failed 重点&#xff01;&#xff01;&#xff01; 此方案会删除数据的&#xff01;&#xff01;&#xff01; 类似重置一样&#xff01; 报错 查看日志&#xff1a;/var/log/mysqld.log 解决方案 查看配置文…

Python热频随机森林分类器算法模型模拟

&#x1f3af;要点 研究发射测量斜率和时滞热频率表征&#xff0c;使用外推法计算三维磁场并定性比较使用基于焓的热演化环模型模拟每条线的热力学响应&#xff0c;测试低频、中频和高频热场景使用光学薄、高温、低密度等离子体的单位体积辐射功率或发射率公式等建模计算使用直…

动手学深度学习(四)卷积神经网络-下

全连接层存在的问题&#xff1a;参数过大&#xff0c;计算成本过高。 一、网络中的网络&#xff08;NiN&#xff09; 1、NiN块 ①NiN块的结构 NiN串联多个由卷积层和“全连接”层构成的小网络来构建一个深层网络。这种由卷积层和“全连接”层构成的小网络就是NiN块。 &#…

SpringBoot框架之KOB项目 - 配置Mysql与注册登录模块(上)

框架模型 每一个客户端&#xff08;client&#xff09;都会和后端&#xff08;SpringBoot&#xff09;进行通信&#xff0c;例如如果一个用户进行登录&#xff0c;需要向后端发送username、password&#xff0c;SpringBoot可以理解为一个一直在跑的程序&#xff0c;不断对用户…

3.Java高级编程实用类介绍(一)

三、Java高级编程实用类介绍(一) 文章目录 三、Java高级编程实用类介绍(一)一、枚举类型二、包装类三、Math 一、枚举类型 使用enum进行定义 public enum 枚举名字{值1,值2.... }二、包装类 每个基本类型在java.lang包中都有一个相应的包装类 /** new包装类&#xff08;字符…