数据结构 —— 队列

队列是一种重要的线性数据结构,遵循 "先进先出"(FIFO,First In First Out)的原则,即最早进入队列的元素最先被取出。队列广泛应用于调度系统、缓冲处理、广度优先搜索等场景。在算法中,巧妙地利用队列这种数据结构可以很有效地解决问题,在实际的工程应用中,有很多的技术也用到了队列这种数据结构。

一、队列的基本概念

  • **队头(Front):**队列中第一个元素的位置,是元素出队的一端。
  • **队尾(Rear):**队列中最后一个元素的位置,是元素入队的一端。
  • 基本操作:
  • 入队(Enqueue):在队尾添加元素。
  • 出队(Dequeue):从队头移除元素。
  • 查看队头(Peek/Front):返回队头元素(不删除)。
  • 判空(IsEmpty):检查队列是否为空。
  • 求长度(Size):返回队列中元素的个数。

二、队列的实现方式

队列的实现通常基于数组或链表,两种方式各有优劣:

1. 基于数组的队列(顺序队列)

  • 原理 :用固定大小的数组存储元素,通过两个指针(frontrear)分别指向队头和队尾。
  • 问题 :随着元素的入队和出队,frontrear 会逐渐后移,最终导致数组 "假溢出"(即数组末尾有空间,但因 rear 已达边界无法入队)。
  • 优化 :采用循环队列 (环形队列),将数组首尾相连,当 rear 到达数组末尾时,若前方有空位则绕回头部,解决假溢出问题。
  • 循环队列的关键操作
  • 入队:rear = (rear + 1) % 容量
  • 出队:front = (front + 1) % 容量
  • 判空:front == rear
  • 判满:(rear + 1) % 容量 == front(这里牺牲一个位置区分空和满)

代码实现(循环队列):

java 复制代码
public class CircularQueue {
    private int[] queue;    // 存储元素的数组
    private int front;      // 队头指针(指向第一个元素)
    private int rear;       // 队尾指针(指向最后一个元素的下一位)
    private int capacity;   // 队列容量
    private int size;       // 当前元素数量

    // 初始化队列
    public CircularQueue(int capacity) {
        this.capacity = capacity;
        queue = new int[capacity];
        front = 0;
        rear = 0;
        size = 0;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 判断队列是否已满
    public boolean isFull() {
        return size == capacity;
    }

    // 入队操作(队尾添加元素)
    public void enqueue(int item) {
        if (isFull()) {
            throw new RuntimeException("队列已满,无法入队");
        }
        queue[rear] = item;
        rear = (rear + 1) % capacity;  // 队尾指针循环后移
        size++;
    }

    // 出队操作(队头移除元素)
    public int dequeue() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空,无法出队");
        }
        int item = queue[front];
        front = (front + 1) % capacity;  // 队头指针循环后移
        size--;
        return item;
    }

    // 查看队头元素(不删除)
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空,无元素");
        }
        return queue[front];
    }

    // 获取队列当前元素数量
    public int size() {
        return size;
    }

    // 测试示例
    public static void main(String[] args) {
        CircularQueue queue = new CircularQueue(3);
        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);
        System.out.println(queue.dequeue());  // 输出:1
        queue.enqueue(4);
        System.out.println(queue.peek());     // 输出:2
        System.out.println(queue.size());     // 输出:3
    }
}

2. 基于链表的队列(链式队列)

  • 原理:用链表存储元素,队头指向链表头节点,队尾指向链表尾节点。
  • 优势:无需预先指定容量,动态扩容,避免溢出问题,实现简单。
  • 劣势:相比数组,链表的指针操作会带来额外的内存开销。

代码实现(基于链表):

java 复制代码
// 链表节点类
class Node {
    int data;       // 节点数据
    Node next;      // 指向下一个节点的指针

    public Node(int data) {
        this.data = data;
        this.next = null;
    }
}

// 链式队列实现
public class LinkedQueue {
    private Node front;  // 队头指针(指向第一个节点)
    private Node rear;   // 队尾指针(指向最后一个节点)
    private int size;    // 当前元素数量

    // 初始化队列
    public LinkedQueue() {
        front = null;
        rear = null;
        size = 0;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 入队操作(队尾添加元素)
    public void enqueue(int item) {
        Node newNode = new Node(item);
        if (isEmpty()) {
            // 空队列时,队头和队尾指向同一个节点
            front = newNode;
            rear = newNode;
        } else {
            // 非空队列时,新节点接在队尾,更新队尾指针
            rear.next = newNode;
            rear = newNode;
        }
        size++;
    }

    // 出队操作(队头移除元素)
    public int dequeue() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空,无法出队");
        }
        int item = front.data;
        front = front.next;  // 队头指针后移
        size--;
        // 若队列清空,需将队尾指针也置空
        if (isEmpty()) {
            rear = null;
        }
        return item;
    }

    // 查看队头元素(不删除)
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空,无元素");
        }
        return front.data;
    }

    // 获取队列当前元素数量
    public int size() {
        return size;
    }

    // 测试示例
    public static void main(String[] args) {
        LinkedQueue queue = new LinkedQueue();
        queue.enqueue(10);
        queue.enqueue(20);
        queue.enqueue(30);
        System.out.println(queue.dequeue());  // 输出:10
        System.out.println(queue.peek());     // 输出:20
        System.out.println(queue.size());     // 输出:2
    }
}

三、Java中队列的实现

由于作者是一位Java后端开发人员,所以这里主要以Java中队列的实现形式来详细介绍队列的扩展功能。

在Java 中主要通过java.util.Queue接口规范了队列的基本操作,并提供了多种实现类,适用于不同场景。

Java 中的 Deque(Double-Ended Queue的缩写) 接口是双端队列,它继承自 Queue 接口,其实现主要有 ArrayDeque 、 LinkedList 、ConcurrentLinkedDeque、BlockingDeque(接口)。

一、Queue 接口中核心方法

Queue 接口是继承自 Collection 接口,有如下的队列核心操作:

|----------|-------------|----------------|---------------------------------|
| 操作类型 | 方法 | 说明 | 异常情况 |
| 入队 | add(e) | 在队尾添加元素 | 队列满时抛出IllegalStateException |
| 入队 | offer(e) | 在队尾添加元素(推荐) | 队列满时返回false |
| 出队 | remove() | 移除并返回队头元素 | 队列为空时抛出NoSuchElementException |
| 出队 | poll() | 移除并返回队头元素(推荐) | 队列为空时返回null |
| 查看队头 | element() | 返回队头元素(不移除) | 队列为空时抛出NoSuchElementException |
| 查看队头 | peek() | 返回队头元素(不移除,推荐) | 队列为空时返回null |

二、Queue 的主要实现类

1.LinkedList(链表队列)
  • 底层结构:双向链表。
  • 特点
  • 实现了QueueDeque接口,可作为队列或双端队列使用。
  • 无容量限制(理论上受内存限制),不会出现队列满的情况。
  • 入队 / 出队操作效率为O(1),但因链表节点的内存开销,性能略低于数组实现。
  • 适用场景:需要动态扩容、不确定元素数量的场景。
java 复制代码
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);   // 入队
queue.offer(2);
System.out.println(queue.poll());  // 出队:1
System.out.println(queue.peek());  // 查看队头:2
2.ArrayDeque(双端队列)
  • 底层结构:动态扩容的循环数组。
  • 特点
  • 实现了Deque接口,支持两端入队 / 出队(兼具队列和栈的功能)。
  • 初始容量为 8,当元素满时自动扩容(翻倍)。
  • 入队 / 出队操作效率为O(1),数组结构减少了链表的指针开销,性能优于LinkedList
  • 适用场景:需要高效双端操作、频繁入队出队的场景(推荐作为队列 / 栈的首选)。
java 复制代码
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(10);
queue.offer(20);
System.out.println(queue.poll());  // 10
3.PriorityQueue(优先级队列)
  • 底层结构:基于二叉堆(小顶堆)。
  • 特点
  • 不遵循 FIFO,而是按元素优先级排序(默认自然顺序,可自定义Comparator)。
  • 队头始终是优先级最高的元素(如最小元素)。
  • 入队操作O(log n),出队操作O(log n)
  • 适用场景:需要按优先级处理任务的场景(如调度系统)
java 复制代码
// 默认小顶堆(元素从小到大出队)
Queue<Integer> pq = new PriorityQueue<>();
pq.offer(3);
pq.offer(1);
pq.offer(2);
System.out.println(pq.poll());  // 1(最小元素)
System.out.println(pq.poll());  // 2

// 自定义大顶堆(元素从大到小出队)
Queue<Integer> maxPq = new PriorityQueue<>((a, b) -> b - a);
maxPq.offer(3);
maxPq.offer(1);
maxPq.offer(2);
System.out.println(maxPq.poll());  // 3(最大元素)
4.BlockingQueue(阻塞队列)

BlockingQueue 是继承自 Queue 的子接口,增加了阻塞操作 (当队列满 / 空时,线程会阻塞等待),专为多线程协作设计。

主要实现类有:

|-------------------------|---------------------------------------------|
| 实现类 | 特点 |
| ArrayBlockingQueue | 基于数组的有界阻塞队列,容量固定,支持公平 / 非公平锁。 |
| LinkedBlockingQueue | 基于链表的阻塞队列,默认容量为Integer.MAX_VALUE (可视为无界)。 |
| SynchronousQueue | 无容量队列,入队操作必须等待出队操作完成(同步传递元素)。 |
| PriorityBlockingQueue | 带优先级的无界阻塞队列,按优先级排序。 |

核心阻塞方法

  • put(e):队列满时阻塞,直到有空间。
  • take():队列为空时阻塞,直到有元素。
java 复制代码
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
// 入队(满时阻塞)
new Thread(() -> {
    try {
        blockingQueue.put(1);
        blockingQueue.put(2);
        blockingQueue.put(3);  // 队列满,阻塞等待
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

// 出队(空时阻塞)
new Thread(() -> {
    try {
        Thread.sleep(1000);
        System.out.println(blockingQueue.take());  // 1,唤醒入队线程
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

三、队列应用

  1. 任务调度 :如线程池中的任务队列(ThreadPoolExecutor使用BlockingQueue存储待执行任务)。
  2. 消息队列:分布式系统中,用队列缓冲消息(如 Kafka、RabbitMQ 的底层思想)。
  3. 广度优先搜索(BFS):遍历树或图时,用队列存储待访问节点。
  4. 缓冲处理:如 IO 流中的缓冲区,平衡数据读写速度差异。
  • 单线程场景:优先用ArrayDeque(高效)或LinkedList(灵活)。
  • 优先级需求:用PriorityQueue
  • 多线程并发:非阻塞用ConcurrentLinkedQueue,阻塞用ArrayBlockingQueueLinkedBlockingQueue
  • 固定容量:用ArrayBlockingQueueArrayDeque(手动指定初始容量)。
相关推荐
Bear on Toilet2 小时前
C++_Bug:现代写法拷贝构造中 swap 写法之小坑
数据结构·c++·bug
潼心1412o3 小时前
数据结构(长期更新)第8讲:队列
数据结构
fashion 道格4 小时前
C 语言希尔排序:原理、实现与性能深度解析
数据结构·算法·排序算法
如意猴4 小时前
实现链式结构二叉树--递归中的暴力美学(第13讲)
数据结构
初夏睡觉4 小时前
P1048 [NOIP 2005 普及组] 采药
数据结构·c++·算法
Zero不爱吃饭5 小时前
环形链表(C)
数据结构·链表
xiaoye-duck5 小时前
数据结构之二叉树-链式结构(下)
数据结构·算法
Kt&Rs5 小时前
11.13 LeetCode 题目汇总与解题思路
数据结构·算法
yuuki2332336 小时前
【数据结构】常见时间复杂度以及空间复杂度
c语言·数据结构·后端·算法