【数据结构】栈(Stack)

定义:在Java编程语言中,栈(Stack)是一种非常重要的数据结构,具有后进先出的特性,即最后入栈的元素最先出栈。栈通常用于存储临时性的数据,如方法调用过程中的局部遍历、操作数栈等。

图像理解

我们在这里要理解栈顶和栈底。这里的"顶"和"底"与我们常识中的顶端和底端是相反的。

栈顶(Top):栈顶是栈中最后加入的元素的位置。在栈的操作中,所有入栈(push)和出栈(pop)的操作都是针对栈顶元素进行的

栈底(Bottom):栈底是栈中最早加入的元素的位置(初始为空)。栈底的元素是最后会被出栈的元素,如果栈中没有进行任何出栈操作的话。

语法定义

public class Stack<E> extends Vector<E>

Stack类是java.util包的一部分,且继承自Vector,因此它拥有Vector的所有方法,并增加了一些专门为栈设计的方法。后续会讲解Vector类

主要方法

1.push(E item):将元素压入栈顶。

2.pop():移除并返回栈顶元素。如果栈为空,则抛出EmptyStackException

3.peek():返回栈顶元素,但不移除它。如果栈为空,则抛出EmptyStackException

4.empty():测试堆栈是否为空。如果栈为空,则返回true;否则返回false

5.search(Object o):返回对象在堆栈中的索引。如果对象o在栈中,则返回其从1开始的索引;如果不在栈中,则返回-1。注意,这里的索引是从栈底到栈顶的顺序计算的

此外,Stack类还继承了Vector类的其他方法,如size()、add(E e)、remove(Object o)等,这些方法可以用于获取栈的大小、向栈中添加元素或从栈中移除元素等操作。但需要注意的是,由于Stack类主要是为了实现栈的数据结构,因此一些Vector类的方法在Stack类中的使用可能并不直观或必要

代码示例:

public static void main(String[] args) {
        // 创建一个栈
        Stack<Integer> stack = new Stack<>();
        // 入栈
        stack.push(10);//栈顶
        stack.push(20);
        stack.push(30);//栈底
        System.out.println("Stack after pushes: " + stack);// 输出: [10, 20, 30]
        // 查看栈顶元素
        System.out.println("Top element: " + stack.peek()); // 输出: 30
        // 出栈
        System.out.println("Popped element: " + stack.pop()); // 输出: 30
        System.out.println("Stack after pop: " + stack); // 输出: [10, 20]
        // 检查是否为空
        System.out.println("Is stack empty?" + stack.isEmpty()); // 输出: false
        //查找元素:注意这里索引是从1开始的,而且是从栈底到栈顶的顺序计算的
        System.out.println("Position of 10: " + stack.search(10)); // 输出: 2
    }

复杂度分析

栈的基本操作(如入栈、出栈、访问栈顶元素、判断栈是否为空等)的时间复杂度通常为O(1),因为这些操作只需要在栈顶进行,无需遍历整个栈。然而,需要注意的是,这些复杂度分析是基于常规实现方式的情况下给出的,具体实现方式可能会影响复杂度

栈的实现方式

1.基于简单数组的实现

·原理:使用数组作为底层数据结构,通过维护一个栈顶指针来跟踪栈顶元素的位置

·优点:实现简单,访问速度快

·缺点:数组长度固定,需要提前定义栈的最大容量

2.基于动态数组的实现

·原理:使用动态数组(如ArrayList)作为底层数据结构,通过动态调整数组大小来实现栈的功能

·优点:可以动态调整容量,无需提前定义栈的最大容量

·缺点:在扩容和缩容时可能会设计额外的内存分配和复制操作

3.基于链表的实现

·原理:使用链表作为底层数据结构,通过链表的节点来存储栈中的元素

·优点:无需提前定义栈的最大容量,可以动态调整栈的大小

·缺点:访问速度相对较慢,因为需要遍历链表来查找栈顶元素

注意:

我们上述代码中Stack<Integer> stack=new Stack<>();创建的栈Stack底层存储的是一个数组,它会在需要时自动增长。这里说的数组其实就是Vector。在Java中,Stack类继承自Vector类,而Vector类本身是一个动态数组的实现。

尽管Stack提供了线程安全的栈实现,但由于它是基于Vector的,因此可能不是性能最优的选择。在需要高性能栈实现的场景中,可以考虑使用其他线程安全的集合类(如java.util.concurrent包中的类)或自己实现一个基于数组或链表的线程安全栈。同时,对于不需要线程安全的场景,ArrayList或其他非同步集合类可能提供更好的性能。

应用场景

Stack类适用于处理具有递归特性或需要逆序操作的问题。以下是一些常见的应用场景

1.方法调用和返回:在程序执行过程中,方法调用时会使用栈的特性,将方法调用的信息存储在栈帧中,然后依次执行,直到返回

2.表达式求值:计算机编辑器和解释器在求解表达式时会使用栈来保存操作数和运算符。例如:LeetCode的逆波兰表达式求值:150. 逆波兰表达式求值 - 力扣(LeetCode)

3.撤销操作:编辑器和设计软件通常使用栈来实现撤销和重做的功能

4.括号匹配:可以使用栈来验证表达式中的括号是否匹配

5.深度优先搜索(DFS):在实现图的遍历算法时,可以使用栈来保存待访问的节点

注意事项

·线程安全:由于Stack继承自Vector,并且Vector的方法都被synchronized关键字保护,因此Stack类是线程安全的。但在单线程环境下,使用线程安全的Stack类可能会导致性能下降。此时,可以考虑使用性能更高的Deque(如ArrayDeque)来代替Stack。后续会讲解Deque(双端队列)接口。

内存溢出:如果栈使用不当(如无限制地入栈而不出栈),可能会导致内存溢出问题。因此,在使用栈时需要注意栈的大小和元素的出入栈操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值