Java中的栈与队列

已同步发布于我的博客

本文主要记述 Java在实践中如何使用栈,关于栈与队列的基础知识点本文不再赘述,可参考 编程帮-栈编程帮-队列

标准库 java.util.Stack 实现

java.util 中封装了 Stack 类,其继承自Vector类,定义了push、pop、peek、empty、search五个操作对Vector进行了扩展,JDK源码(已省去jdoc)如下:

scss 复制代码
 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;
     }
 ​
     /** use serialVersionUID from JDK 1.0.2 for interoperability */
     @java.io.Serial
     private static final long serialVersionUID = 1224463164541339165L;
 }

Deque接口实现

在Java中,栈可以使用java.util.Stack类实现,也可以使用 java.util.LinkedList 结合pushpop操作来实现自定义的栈。通常,建议使用 Deque接口中的 LinkedList 实现来代替java.util.Stack,因为后者是基于向量的数据结构,性能较差。

示例代码:

arduino 复制代码
 public class StackByDeque {
 ​
     public static void main(String[] args) {
         Deque<Integer> stack = new LinkedList<>();
         stack.push(1);
         stack.push(2);
         int top = stack.pop();
         System.out.println(top + " " + stack.size());
     }   
 }

其中,值得注意的是 LinkedList 类。LinkedList是Java中的一个双向链表实现,它继承自AbstractSequentialList类,并实现了ListDeque接口,同时提供了双向链表的功能。JDK中的源码如下:

csharp 复制代码
 public class LinkedList<E>
     extends AbstractSequentialList<E>
     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
 {
     transient int size = 0;
 ​
     /**
      * Pointer to first node.
      */
     transient Node<E> first;
 ​
     /**
      * Pointer to last node.
      */
     transient Node<E> last;
     ...
         

LinkedList 类有以下的特点:

  1. 双向链表 : LinkedList是基于双向链表数据结构的实现,每个元素都被包装在一个节点中,而每个节点都包含指向前一个节点和后一个节点的引用。这使得在链表中插入和删除元素的操作非常高效,因为只需要调整节点的引用,而不需要移动其他元素。
  2. 实现了List接口 : LinkedList实现了List接口,这意味着它可以像ArrayList一样用于存储有序的元素集合,支持随机访问、按索引插入和删除元素。与ArrayList不同,LinkedList在插入和删除操作上通常更高效,但在随机访问上性能较差。
  3. 实现了Deque接口 : LinkedList还实现了Deque接口,这使得它可以被用作双端队列(double-ended queue)。您可以在队列的前端和后端进行插入和删除操作,支持先进先出(FIFO)和后进先出(LIFO)等不同的数据操作模式。
  4. 适合用于栈和队列 : 由于LinkedList支持双向操作,它特别适合用作栈和队列的基础数据结构。您可以使用pushpop操作来实现栈,使用offerpoll操作来实现队列。
  5. 非线程安全 : 与ArrayList不同,LinkedList是非线程安全的,这意味着在多线程环境下需要进行额外的同步处理,或者使用java.util.concurrent包中的并发数据结构来保证线程安全。
  6. 动态大小 : LinkedList的大小是动态的,它会根据需要动态分配内存。这意味着它可以容纳任意数量的元素,但需要根据实际使用情况分配和释放内存,可能会引入一些内存管理开销。

LinkedList是一个灵活的双向链表实现,适合用于需要频繁插入和删除操作的情况,以及栈和队列等数据结构的实现。但需要注意,对于需要随机访问的情况,ArrayList通常更为高效。

自定义实现

1. 使用数组

可以使用数组来实现栈,通过维护一个指向栈顶元素的指针(通常称为top),以及数组来存储栈的元素。当进行pushpop操作时,相应地更新栈顶指针和数组元素。这种实现方式的优点是简单,但需要注意数组大小的限制。

arduino 复制代码
 public class ArrayStack {
     private int maxSize;   // 栈的最大容量
     private int top;       // 栈顶指针
     private int[] stack;   // 存储元素的数组
 ​
     public ArrayStack(int maxSize) {
         this.maxSize = maxSize;
         this.stack = new int[maxSize];
         this.top = -1; // 栈开始时为空
     }
 ​
     // 判断栈是否为空
     public boolean isEmpty() {
         return top == -1;
     }
 ​
     // 判断栈是否已满
     public boolean isFull() {
         return top == maxSize - 1;
     }
 ​
     // 入栈
     public void push(int item) {
         if (isFull()) {
             System.out.println("栈已满,无法入栈");
             return;
         }
         stack[++top] = item;
     }
 ​
     // 出栈
     public int pop() {
         if (isEmpty()) {
             System.out.println("栈为空,无法出栈");
             return -1; // 返回一个特定的值表示栈为空
         }
         return stack[top--];
     }
 ​
     // 查看栈顶元素
     public int peek() {
         if (isEmpty()) {
             System.out.println("栈为空,无法查看栈顶元素");
             return -1; // 返回一个特定的值表示栈为空
         }
         return stack[top];
     }
 ​
     public static void main(String[] args) {
         ArrayStack stack = new ArrayStack(5);
 ​
         stack.push(1);
         stack.push(2);
         stack.push(3);
 ​
         System.out.println("栈顶元素:" + stack.peek());
 ​
         while (!stack.isEmpty()) {
             System.out.println("出栈元素:" + stack.pop());
         }
     }
 }
 ​

2. 使用链表:

类似于使用数组,您可以使用链表来实现栈。链表的头节点可以充当栈顶,而pushpop操作将在链表头部进行。这种实现方式通常不会受到数组大小的限制,并且在动态大小方面更加灵活。

arduino 复制代码
 public class LinkedListStack<T> {
     private Node<T> top; // 栈顶节点
 ​
     private static class Node<T> {
         T data;
         Node<T> next;
 ​
         public Node(T data) {
             this.data = data;
         }
     }
 ​
     // 判断栈是否为空
     public boolean isEmpty() {
         return top == null;
     }
 ​
     // 入栈
     public void push(T item) {
         Node<T> newNode = new Node<>(item);
         newNode.next = top;
         top = newNode;
     }
 ​
     // 出栈
     public T pop() {
         if (isEmpty()) {
             throw new IllegalStateException("栈为空,无法出栈");
         }
         T data = top.data;
         top = top.next;
         return data;
     }
 ​
     // 查看栈顶元素
     public T peek() {
         if (isEmpty()) {
             throw new IllegalStateException("栈为空,无法查看栈顶元素");
         }
         return top.data;
     }
 ​
     public static void main(String[] args) {
         LinkedListStack<Integer> stack = new LinkedListStack<>();
 ​
         stack.push(1);
         stack.push(2);
         stack.push(3);
 ​
         System.out.println("栈顶元素:" + stack.peek());
 ​
         while (!stack.isEmpty()) {
             System.out.println("出栈元素:" + stack.pop());
         }
     }
 }
 ​

3. 使用单链表节点

如果不需要支持pop操作,只需要实现pushpeek,您可以使用单链表节点来创建一个简化的栈。每个节点包含数据和一个指向下一个节点的引用。

arduino 复制代码
 public class SingleLinkedListStack<T> {
     private Node<T> top; // 栈顶节点
 ​
     private static class Node<T> {
         T data;
         Node<T> next;
 ​
         public Node(T data) {
             this.data = data;
         }
     }
 ​
     // 判断栈是否为空
     public boolean isEmpty() {
         return top == null;
     }
 ​
     // 入栈
     public void push(T item) {
         Node<T> newNode = new Node<>(item);
         newNode.next = top;
         top = newNode;
     }
 ​
     // 出栈
     public T pop() {
         if (isEmpty()) {
             throw new IllegalStateException("栈为空,无法出栈");
         }
         T data = top.data;
         top = top.next;
         return data;
     }
 ​
     // 查看栈顶元素
     public T peek() {
         if (isEmpty()) {
             throw new IllegalStateException("栈为空,无法查看栈顶元素");
         }
         return top.data;
     }
 ​
     public static void main(String[] args) {
         SingleLinkedListStack<Integer> stack = new SingleLinkedListStack<>();
 ​
         stack.push(1);
         stack.push(2);
         stack.push(3);
 ​
         System.out.println("栈顶元素:" + stack.peek());
 ​
         while (!stack.isEmpty()) {
             System.out.println("出栈元素:" + stack.pop());
         }
     }
 }
 ​

4. 使用双链表节点

如果需要支持pop操作和更高效的poppeek操作,可以使用双链表节点来实现栈。这种实现方式允许从栈顶和栈底执行poppeek操作。

csharp 复制代码
 public class DoubleLinkedListStack<T> {
     private Node<T> top; // 栈顶节点
 ​
     private static class Node<T> {
         T data;
         Node<T> prev;
         Node<T> next;
 ​
         public Node(T data) {
             this.data = data;
         }
     }
 ​
     // 判断栈是否为空
     public boolean isEmpty() {
         return top == null;
     }
 ​
     // 入栈
     public void push(T item) {
         Node<T> newNode = new Node<>(item);
         if (top != null) {
             newNode.next = top;
             top.prev = newNode;
         }
         top = newNode;
     }
 ​
     // 出栈
     public T pop() {
         if (isEmpty()) {
             throw new IllegalStateException("栈为空,无法出栈");
         }
         T data = top.data;
         top = top.next;
         if (top != null) {
             top.prev = null;
         }
         return data;
     }
 ​
     // 查看栈顶元素
     public T peek() {
         if (isEmpty()) {
             throw new IllegalStateException("栈为空,无法查看栈顶元素");
         }
         return top.data;
     }
 ​
     public static void main(String[] args) {
         DoubleLinkedListStack<Integer> stack = new DoubleLinkedListStack<>();
 ​
         stack.push(1);
         stack.push(2);
         stack.push(3);
 ​
         System.out.println("栈顶元素:" + stack.peek());
 ​
         while (!stack.isEmpty()) {
             System.out.println("出栈元素:" + stack.pop());
         }
     }
 }
 ​

自定义栈的实现方式可以根据特定需求选择,但需要注意线程安全和性能方面的考虑。

队列

java.util.Queue 接口实现

Java提供了java.util.Queue接口,是队列的标准接口。可以使用不同的实现类来创建队列,包括:

  • java.util.LinkedList:基于双向链表的实现,支持先进先出(FIFO)操作。
  • java.util.PriorityQueue:优先级队列的实现,元素按照优先级顺序排列。

以下使用java.util.LinkedList来实现队列。offer方法用于将元素入队列,poll方法用于出队列。由于LinkedList实现了Queue接口,可以方便地使用它来实现队列的操作

arduino 复制代码
 public class QueueByLinkedList {
     public static void main(String[] args) {
         Queue<Integer> queue = new LinkedList<>();
 ​
         // 入对列
         queue.offer(1);
         queue.offer(2);
         queue.offer(3);
 ​
         // 出队列
         while (!queue.isEmpty()){
             System.out.println("出队元素: " + queue.poll());
         }
     }
 }

PriorityQueue是一个优先级队列,它根据元素的优先级来排列队列中的元素。较小的元素具有较高的优先级。使用offer方法入队列,poll方法出队列,优先级较高的元素会首先被出队列。

arduino 复制代码
 public class QueueByPriorityQueue {
     public static void main(String[] args) {
         Queue<Integer> queue = new PriorityQueue<>();
 ​
         // 入对列
         queue.offer(1);
         queue.offer(2);
         queue.offer(3);
 ​
         // 出队列
         while (!queue.isEmpty()){
             System.out.println("出队元素: " + queue.poll());
         }
     }
 }

使用 java.util.concurrent 包中的队列

Java的java.util.concurrent包提供了多线程安全的队列实现,用于多线程环境。一些常见的实现包括:

  • java.util.concurrent.LinkedBlockingQueue:基于链表的阻塞队列。
  • java.util.concurrent.ArrayBlockingQueue:基于数组的阻塞队列。
  • java.util.concurrent.PriorityBlockingQueue:多线程安全的优先级队列。

自定义队列

此外,还可以使用数组或链表等数据结构来自定义队列的实现,根据特定需求创建自己的队列类。这通常需要额外的编程工作,但可以满足特定场景的需求。

总结

本文总结了Java中栈和队列的几种实现方式:

  1. 栈的实现:

    • 使用Java标准库中的Stack类
    • 使用Deque接口中的LinkedList实现栈操作
    • 自定义数组栈、链表栈、单链表栈、双链表栈
  2. 队列的实现:

    • 使用Java标准库中的Queue接口及其实现类LinkedList、PriorityQueue
    • 使用java.util.concurrent包下的阻塞队列
    • 自定义使用数组或链表实现的队列
相关推荐
渣哥2 小时前
原来 Java 里线程安全集合有这么多种
java
间彧2 小时前
Spring Boot集成Spring Security完整指南
java
间彧3 小时前
Spring Secutiy基本原理及工作流程
java
Java水解4 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆6 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学6 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole6 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊6 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端
程序员鱼皮7 小时前
刚刚 Java 25 炸裂发布!让 Java 再次伟大
java·javascript·计算机·程序员·编程·开发·代码