《Java数据结构与算法》第三篇(下)队列全解析:从基础概念到高级应用

目录

目录

前言

正文

一、队列(Queue)的定义

[1. Queue 是什么?](#1. Queue 是什么?)

[2. Queue 的继承体系](#2. Queue 的继承体系)

二、java.util包中的队列接口

[1.Queue 的核心操作方法(最关键的部分)](#1.Queue 的核心操作方法(最关键的部分))

两种方法的区别:

[2. 常见的 Queue 实现类](#2. 常见的 Queue 实现类)

1.LinkedList

2.PriorityQueue

3.ArrayDeque

3.线程安全的队列

4.使用示例

三、顺序队列

[1. 顺序队列的表示](#1. 顺序队列的表示)

[2. 顺序队列的假溢出](#2. 顺序队列的假溢出)

[3. 顺序循环队列](#3. 顺序循环队列)

四、双端队列(Deque)

五、链式队列

链式队列的实现

链式队列的特点

六、对比

七、用队列实现杨辉三角

1.介绍

2.代码实现

3.结果演示​编辑

总结

致谢


前言

队列(Queue)作为计算机科学中最重要的数据结构之一,是每一个程序员必须掌握的基础知识。无论是在操作系统中的进程调度、网络数据包的处理,还是日常开发中的任务管理,队列都扮演着不可或缺的角色。本文将从队列的基本概念出发,深入剖析Java集合框架中的队列接口及其实现,详细讲解顺序队列、链式队列等不同实现方式的原理与特点,并通过杨辉三角等实际案例展示队列的典型应用。无论你是刚入门的编程新手,还是希望巩固数据结构知识的开发者,相信本文都能为你提供有价值的参考和启发。

正文

一、队列(Queue)的定义

1. Queue 是什么?

Queue(队列)是 Java 集合框架(Java Collections Framework)中的一个重要接口,它位于 java.util包下。它专门用于在处理之前保存元素。

队列的核心特性是遵循 **FIFO(First-In-First-Out,先进先出)**​ 原则。你可以把它想象成现实生活中的排队:先来的人先接受服务。

重要提示 : 虽然 FIFO 是最常见的规则,但并非所有 Queue的实现都严格遵守。例如,PriorityQueue会根据元素的优先级进行排序,而不是插入顺序。

2. Queue 的继承体系

java 复制代码
java.util.Collection
      ↑
java.util.Queue
      ↑
java.util.Deque (子接口,代表双端队列)

Queue接口直接继承自 Collection接口,这意味着所有集合的基本操作(如 add, remove, size, isEmpty等)它都拥有。DequeQueue的子接口,提供了在两端进行操作的能力。

二、java.util包中的队列接口

1.Queue 的核心操作方法(最关键的部分)

Queue接口为基本操作(插入、移除、检查)各提供了两种形式的方法。这两种形式的行为在遇到异常情况(如空队列或容量限制)时完全不同。这是学习 Queue的重中之重。

操作类型 抛出异常的方法 返回特殊值的方法 说明
**插入(Insert)**​ boolean add(E e) boolean offer(E e) 向队列尾部插入一个元素。
**移除(Remove)**​ E remove() E poll() 获取并移除队列头部的元素。
**检查(Examine)**​ E element() E peek() 获取但不移除队列头部的元素。
两种方法的区别:
  • 抛出异常组(Throws exception)

    • 如果操作失败(例如,向一个已满的队列 add,或从一个空队列 remove),这些方法会立即抛出异常(通常是 IllegalStateExceptionNoSuchElementException)。

    • 适用于你确信操作会成功的场景。

  • 返回特殊值组(Returns special value)

    • 如果操作失败,这些方法不会抛出异常,而是返回一个特殊值(falsenull)。

    • 适用于在容量受限的队列中,或者需要优雅处理失败情况的场景(例如,多线程环境)。

简单记忆 :带有 offer, poll, peek的方法更"友好",因为它们用返回值代替了异常。

2. 常见的 Queue 实现类

Queue是一个接口,你需要使用它的实现类。以下是 java.util包中几个最重要的实现:

1.LinkedList

最常用的通用队列实现。

它实现了 List接口,也实现了 Queue接口。

没有容量限制(除非内存耗尽)。

基于链表实现。

注意LinkedList也实现了 Deque接口,所以它可以作为双端队列使用。

java 复制代码
Queue<String> queue = new LinkedList<>();
queue.offer("A");
queue.offer("B");
String first = queue.poll(); // 返回 "A"
2.PriorityQueue

不遵循 FIFO

元素根据其自然顺序 (例如,数字从小到大,字符串按字典序)或通过构造时提供的 **Comparator**​ 进行排序。

出队(poll)时,总是返回当前队列中优先级最高的元素。

没有容量限制

内部基于堆(Heap)数据结构实现。

java 复制代码
// 自然顺序(从小到大)
Queue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(5);
priorityQueue.offer(1);
priorityQueue.offer(3);
while (!priorityQueue.isEmpty()) {
    System.out.println(priorityQueue.poll()); // 输出顺序:1, 3, 5
}
3.ArrayDeque

一个基于可调整大小数组实现的双端队列。

作为队列使用时,性能通常比 LinkedList更好。

没有容量限制

它不能存储 null元素。

通常作为栈(Stack)或双端队列(Deque)使用时的首选

java 复制代码
Queue<String> queue = new ArrayDeque<>();
queue.offer("X");
queue.offer("Y");
String next = queue.poll(); // 返回 "X"

3.线程安全的队列

在这里,先给大家画个饼,关于线程,我们等到以后开线程专栏再讲,这里只做提及,让大家记得有这个东西。包括在接口中讲的数据库,也是会开专栏的。顺带提一嘴,我们在半小时漫画计算机中很早就提及过线程,在不同的地方遇到这些东西的时候就会有一种城市羡慕连接的感觉。

4.使用示例

java 复制代码
import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // 1. 创建一个队列(使用 LinkedList 实现)
        Queue<String> waitingQueue = new LinkedList<>();

        // 2. 向队列添加元素 (推荐使用 offer)
        waitingQueue.offer("张三");
        waitingQueue.offer("李四");
        waitingQueue.offer("王五");
        waitingQueue.add("赵六"); // 也可以用 add

        System.out.println("等待队列: " + waitingQueue); // 输出: [张三, 李四, 王五, 赵六]

        // 3. 查看队列头部的元素(不移除)
        String firstPerson = waitingQueue.peek();
        System.out.println("正在服务: " + firstPerson); // 输出: 张三
        System.out.println("当前队列: " + waitingQueue); // 队列不变

        // 4. 移除并返回队列头部的元素(服务完毕)
        String servedPerson = waitingQueue.poll();
        System.out.println("已服务: " + servedPerson); // 输出: 张三
        System.out.println("剩余队列: " + waitingQueue); // 输出: [李四, 王五, 赵六]

        // 5. 循环处理队列中的所有元素
        System.out.println("开始服务剩余顾客:");
        while (!waitingQueue.isEmpty()) {
            String person = waitingQueue.poll();
            System.out.println("  - 正在服务: " + person);
        }
        System.out.println("最终队列为空: " + waitingQueue.isEmpty()); // 输出: true

        // 6. 尝试从空队列中 poll 和 remove
        System.out.println("从空队列poll: " + waitingQueue.poll()); // 输出: null (友好)
        // String result = waitingQueue.remove(); // 这行会抛出 NoSuchElementException 异常
    }
}

三、顺序队列

1. 顺序队列的表示

顺序队列使用数组作为底层存储结构,通过两个指针frontrear来分别指向队头和队尾

java 复制代码
public class SqQueue<T> {
    final int QUEUESIZE = 20;
    T s[];
    int front, rear;
    
    SqQueue() {
        front = 0;
        rear = 0;
        s = (T[]) new Object[QUEUESIZE];
    }
    
    // 判断队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }
    
    // 判断队列是否已满
    public boolean isFull() {
        return (rear + 1) % QUEUESIZE == front;
    }
    
    // 入队操作
    public boolean offer(T item) {
        if (isFull()) {
            return false; // 队列已满
        }
        s[rear] = item;
        rear = (rear + 1) % QUEUESIZE;
        return true;
    }
    
    // 出队操作
    public T poll() {
        if (isEmpty()) {
            return null;
        }
        T item = s[front];
        front = (front + 1) % QUEUESIZE;
        return item;
    }
    
    // 获取队头元素
    public T peek() {
        if (isEmpty()) {
            return null;
        }
        return s[front];
    }
}

2. 顺序队列的假溢出

问题描述:

假溢出是指队列的存储空间实际上还有空闲位置,但由于队列的线性存储结构导致无法继续插入新元素的现象。

产生原因:

在普通顺序队列中,元素入队时rear指针后移,出队时front指针后移

rear指针到达数组末尾时,即使数组前面有空闲位置(已出队的元素位置),也无法继续插入新元素

这造成了存储空间的浪费

java 复制代码
初始状态:front=0, rear=0
[null][null][null][null][null]

执行入队A,B,C:front=0, rear=3
[A][B][C][null][null]

执行出队A:front=1, rear=3
[已出队][B][C][null][null]

此时虽然数组前部有空位,但rear=3已到末尾,无法继续插入新元素 → 假溢出

3. 顺序循环队列

为了解决假溢出问题,我们引入循环队列的概念,将数组视为一个环状结构。

循环队列的实现要点:

  1. 队空条件:front == rear

  2. 队满条件:(rear + 1) % QUEUESIZE == front

  3. 入队操作:rear = (rear + 1) % QUEUESIZE

  4. 出队操作:front = (front + 1) % QUEUESIZE

循环队列示例:

java 复制代码
public class CircularQueue<T> {
    private T[] queue;
    private int front;
    private int rear;
    private int size;
    
    public CircularQueue(int capacity) {
        queue = (T[]) new Object[capacity];
        front = 0;
        rear = 0;
        size = 0;
    }
    
    public boolean enqueue(T item) {
        if (isFull()) return false;
        
        queue[rear] = item;
        rear = (rear + 1) % queue.length;
        size++;
        return true;
    }
    
    public T dequeue() {
        if (isEmpty()) return null;
        
        T item = queue[front];
        front = (front + 1) % queue.length;
        size--;
        return item;
    }
    
    public boolean isFull() {
        return size == queue.length;
    }
    
    public boolean isEmpty() {
        return size == 0;
    }
}

四、双端队列(Deque)

双端队列(Double Ended Queue)是一种允许在两端进行插入和删除操作的特殊队列。

主要特点:

可以从队头或队尾插入元素

可以从队头或队尾删除元素

结合了栈和队列的特性

Java中的Deque接口方法:

操作 队头操作 队尾操作
插入 addFirst(e) / offerFirst(e) addLast(e) / offerLast(e)
删除 removeFirst() / pollFirst() removeLast() / pollLast()
查看 getFirst() / peekFirst() getLast() / peekLast()
java 复制代码
Deque<String> deque = new ArrayDeque<>();
deque.offerFirst("A");  // 队头插入
deque.offerLast("B");   // 队尾插入
deque.offerLast("C");

System.out.println(deque.pollFirst()); // 输出A(队头删除)
System.out.println(deque.pollLast());  // 输出C(队尾删除)

五、链式队列

链式队列使用链表作为存储结构,克服了顺序队列的固定容量限制。

链式队列的实现

java 复制代码
public class LinkedQueue<T> {
    private QueueNode<T> front; // 队头指针
    private QueueNode<T> rear;  // 队尾指针
    private int size;
    
    public LinkedQueue() {
        front = rear = null;
        size = 0;
    }
    
    // 入队操作
    public boolean offer(T data) {
        QueueNode<T> newNode = new QueueNode<>(data);
        if (rear == null) {
            // 空队列情况
            front = rear = newNode;
        } else {
            rear.next = newNode;
            rear = newNode;
        }
        size++;
        return true;
    }
    
    // 出队操作
    public T poll() {
        if (front == null) {
            return null;
        }
        T data = front.data;
        front = front.next;
        
        if (front == null) {
            rear = null; // 队列变空,重置rear
        }
        size--;
        return data;
    }
    
    // 获取队头元素
    public T peek() {
        return front == null ? null : front.data;
    }
    
    public boolean isEmpty() {
        return front == null;
    }
    
    public int size() {
        return size;
    }
}

class QueueNode<T> {
    T data;
    QueueNode<T> next;
    
    public QueueNode(T data) {
        this.data = data;
        this.next = null;
    }
}

链式队列的特点

优点:

动态分配内存,无需担心容量限制

插入删除操作的时间复杂度为O(1)

不会出现假溢出问题

缺点:

每个节点需要额外的指针空间

访问需要遍历,不能随机访问

六、对比

队列类型 存储结构 优点 缺点 适用场景
顺序队列 数组 内存连续,访问快 固定容量,假溢出 已知最大容量
循环队列 数组 解决假溢出问题 浪费一个存储单元 需要循环使用的场景
链式队列 链表 动态扩容,无假溢出 额外指针开销 容量不确定的场景
双端队列 数组/链表 操作灵活 实现相对复杂 需要在两端操作的场景

七、用队列实现杨辉三角

1.介绍

杨辉三角,又称帕斯卡三角,是二项式系数在三角形中的一种几何排列,最早可追溯至北宋时期数学家贾宪的"开方作法本源图",而南宋数学家杨辉在《详解九章算法》中详细记载并普及了此图,故在中文数学文献中常被称为"杨辉三角"。其基本规则是每个数等于它上方两数之和(即第n行的第k个数等于第n-1行的第k-1个数与第k个数之和),且每行左右对称(从中间一列开始),由1开始,顶端为第0行,每行数字从第0列开始计数。杨辉三角在组合数学、概率论、代数等领域有广泛应用,例如第n行的数字对应二项式(a+b)^n展开的系数,同时每一行的数字之和等于2的n次方等。

2.代码实现

java 复制代码
import java.util.LinkedList;
import java.util.Queue;

public class YangHuiWithQueue {
    public static void main(String[] args) {
        printYangHuiWithQueue(10);
    }

    public static void printYangHuiWithQueue(int n) {
        if (n <= 0) return;

        Queue<Integer> queue = new LinkedList<>();

        // 第一行
        queue.offer(1);
        System.out.println("1");

        for (int i = 2; i <= n; i++) {
            // 每行的第一个数字是1
            int prev = 0;
            int current;

            // 生成当前行的数字
            for (int j = 0; j < i - 1; j++) {
                current = queue.poll();
                // 计算当前数字:上一行的左上方数字 + 上一行的正上方数字
                queue.offer(prev + current);
                prev = current;
            }
            // 每行的最后一个数字是1
            queue.offer(1);

            // 打印当前行
            printQueue(new LinkedList<>(queue));
        }
    }

    private static void printQueue(Queue<Integer> queue) {
        Queue<Integer> temp = new LinkedList<>(queue);
        while (!temp.isEmpty()) {
            System.out.print(temp.poll() + " ");
        }
        System.out.println();
    }
}

3.结果演示

由于这里排版不是重点,且难度很低,就不进行排版了,减少操作

总结

通过本文的全面讲解,我们对队列这一重要的数据结构有了系统而深入的认识。从基础的FIFO原则到Java集合框架中Queue接口的具体实现,从顺序队列的假溢出问题到循环队列的巧妙解决方案,从链式队列的动态特性到双端队列的灵活操作,我们逐步构建了完整的队列知识体系。

队列不仅仅是数据结构课本中的一个概念,更是解决实际问题的有力工具。通过杨辉三角的实现案例,我们看到了队列在算法设计中的巧妙应用。不同的队列实现各有优劣:顺序队列适合已知容量的场景,链式队列适合动态扩容的需求,双端队列则为特殊操作提供了便利。在实际开发中,我们需要根据具体需求选择最合适的队列类型。

掌握队列的核心在于理解其"先进先出"的本质特征以及各种实现方式的内在原理。希望本文能够帮助读者建立起对队列的深刻理解,为后续学习更复杂的数据结构和算法打下坚实基础。

致谢

在本文的撰写过程中,我要感谢Java官方文档提供的准确技术规范,以及开源社区中众多开发者分享的宝贵经验。同时感谢所有数据结构领域的先驱者,他们的智慧结晶为我们今天的学习提供了丰富的理论支撑。

特别感谢CSDN平台为技术爱好者提供了如此优秀的知识分享环境,让开发者能够相互学习、共同进步。也要感谢每一位坚持阅读到这里的读者,你们的关注和认可是我持续创作的最大动力。如果本文中存在任何疏漏或不足之处,欢迎各位读者批评指正,让我们在技术道路上携手共进。

最后,希望本文能够成为读者学习队列数据结构的有益参考,期待在未来的技术文章中与大家再次相遇!

相关推荐
学习路上_write1 小时前
FREERTOS_互斥量_创建和使用
c语言·开发语言·c++·stm32·单片机·嵌入式硬件
vx_vxbs661 小时前
【SSM电动车智能充电服务平台】(免费领源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案
java·spring boot·mysql·spring cloud·小程序·php·idea
叹隙中驹石中火梦中身1 小时前
解耦神器Event和EventListener
java
Boop_wu1 小时前
[Java EE] 多线程进阶(JUC)(2)
java·jvm·算法
pale_moonlight1 小时前
十、 Scala 应用实践 (上)
大数据·开发语言·scala
小坏讲微服务1 小时前
SpringCloud整合Scala实现MybatisPlus实现业务增删改查
java·spring·spring cloud·scala·mybatis plus
N***p3651 小时前
五大消息模型介绍(RabbitMQ 详细注释版)
java·rabbitmq·java-rabbitmq
6***v4171 小时前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
1***s6321 小时前
Rust在WebAssembly中的应用实践
开发语言·rust·wasm