当前位置: 首页 > news >正文

【数据结构与算法-Day 14】先进先出的公平:深入解析队列(Queue)的核心原理与数组实现

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

Go语言系列文章目录

Docker系列文章目录

数据结构与算法系列文章目录

01-【数据结构与算法-Day 1】程序世界的基石:到底什么是数据结构与算法?
02-【数据结构与算法-Day 2】衡量代码的标尺:时间复杂度与大O表示法入门
03-【数据结构与算法-Day 3】揭秘算法效率的真相:全面解析O(n^2), O(2^n)及最好/最坏/平均复杂度
04-【数据结构与算法-Day 4】从O(1)到O(n²),全面掌握空间复杂度分析
05-【数据结构与算法-Day 5】实战演练:轻松看懂代码的时间与空间复杂度
06-【数据结构与算法-Day 6】最朴素的容器 - 数组(Array)深度解析
07-【数据结构与算法-Day 7】告别数组束缚,初识灵活的链表 (Linked List)
08-【数据结构与算法-Day 8】手把手带你拿捏单向链表:增、删、改核心操作详解
09-【数据结构与算法-Day 9】图解单向链表:从基础遍历到面试必考的链表反转
10-【数据结构与算法-Day 10】双向奔赴:深入解析双向链表(含图解与代码)
11-【数据结构与算法-Day 11】从循环链表到约瑟夫环,一文搞定链表的终极形态
12-【数据结构与算法-Day 12】深入浅出栈:从“后进先出”原理到数组与链表双实现
13-【数据结构与算法-Day 13】栈的应用:从括号匹配到逆波兰表达式求值,面试高频考点全解析
14-【数据结构与算法-Day 14】先进先出的公平:深入解析队列(Queue)的核心原理与数组实现


文章目录

  • Langchain系列文章目录
  • Python系列文章目录
  • PyTorch系列文章目录
  • 机器学习系列文章目录
  • 深度学习系列文章目录
  • Java系列文章目录
  • JavaScript系列文章目录
  • Python系列文章目录
  • Go语言系列文章目录
  • Docker系列文章目录
  • 数据结构与算法系列文章目录
  • 摘要
  • 一、什么是队列(Queue)?
    • 1.1 队列的定义与特性
    • 1.2 队列的核心操作
    • 1.3 队列的抽象数据类型 (ADT)
  • 二、队列的实现方式:顺序队列(基于数组)
    • 2.1 基本思路与数据结构
    • 2.2 核心操作代码实现 (Java)
      • 2.2.1 初始化与成员变量
      • 2.2.2 入队操作 (enqueue)
      • 2.2.3 出队操作 (dequeue)
      • 2.2.4 其他辅助操作
  • 三、顺序队列的痛点:“假溢出”问题
    • 3.1 什么是“假溢出”?
    • 3.2 “假溢出”的成因分析
    • 3.3 如何解决?(引出循环队列)
  • 四、队列的应用场景
    • 4.1 计算机系统中的队列
    • 4.2 算法中的应用:广度优先搜索 (BFS)
    • 4.3 日常生活中的模型
  • 五、总结


摘要

队列(Queue)是计算机科学中最基础也最重要的数据结构之一,它完美地模拟了我们日常生活中“排队”这一行为。本文作为【数据结构与算法】系列的第14篇,将系统性地带你走进“先进先出”(FIFO)的世界。我们将从队列的基本概念和核心操作出发,详细讲解如何使用数组这一最直观的方式来实现队列(即顺序队列)。更重要的是,本文将通过图文并茂的方式,为你揭示顺序队列实现中一个经典且棘手的痛点——“假溢出”问题,并深入剖析其成因。无论你是刚入门的小白,还是希望巩固基础的开发者,本文都将为你打下坚实的队列知识基础,并为后续学习循环队列、双端队列以及广度优先搜索(BFS)等高级主题做好铺垫。


一、什么是队列(Queue)?

在学习了“后进先出”(LIFO)的栈之后,我们今天来认识它的“兄弟”——“先进先出”(FIFO)的队列。

1.1 队列的定义与特性

队列(Queue)是一种特殊的线性表,它只允许在表的一端进行插入操作,而在另一端进行删除操作。

  • 插入端:通常被称为队尾(Rear)
  • 删除端:通常被称为队头(Front)

队列最重要的特性是先进先出(First In, First Out,简称 FIFO)。这个原则非常符合直觉,就像在食堂打饭、在银行排队办理业务一样,最早进入队伍的人,会最先得到服务并离开队伍。

我们可以用一个生活中的例子来类比:

场景类比:单向隧

想象一条只能单向通行的狭窄隧道,汽车从隧道的一端(入口)驶入,然后按照进入的顺序从另一端(出口)驶出。最先进隧道的车,必然最先出隧道。在这个模型中:

  • 隧道:就是我们的队列。
  • 汽车:是队列中存储的元素。
  • 隧道入口:是队尾,新来的车只能从这里进入。
  • 隧道出口:是队头,只有最前面的车能从这里离开。

1.2 队列的核心操作

和栈类似,队列也有一套标准的核心操作,用于管理其内部的元素。

操作描述别名
enqueue(e)将元素 e 加入队尾(入队)add, offer
dequeue()从队头移除并返回元素(出队)remove, poll
peek()查看队头元素,但不移除front, element
isEmpty()判断队列是否为空-
size()返回队列中元素的数量-

这些操作共同构成了队列的抽象数据类型(ADT),定义了队列“应该做什么”,而不关心“如何做”。

1.3 队列的抽象数据类型 (ADT)

我们可以将队列的行为规范定义为一个接口(Interface),这有助于我们从具体实现中解耦。

// 队列的抽象数据类型定义 (Interface)
public interface Queue<E> {/*** 将元素添加到队尾* @param e 待添加的元素* @return 如果操作成功,返回true*/boolean enqueue(E e);/*** 从队头移除并返回元素* @return 队头元素,如果队列为空则返回null*/E dequeue();/*** 查看队头元素(不移除)* @return 队头元素,如果队列为空则返回null*/E peek();/*** 获取队列中元素的个数* @return 元素数量*/int getSize();/*** 判断队列是否为空* @return 如果为空,返回true*/boolean isEmpty();
}

二、队列的实现方式:顺序队列(基于数组)

和栈一样,队列也可以通过数组或链表来实现。我们首先来学习最简单直接的方式——使用数组实现,这种实现方式通常被称为顺序队列(Sequential Queue)

2.1 基本思路与数据结构

要用数组实现队列,我们需要:

  1. 一个数组 data 用于存储元素。
  2. 两个整型变量(或称为“指针”)来分别指向队头和队尾:
    • front:指向队头元素所在的索引。
    • rear:指向下一个可以插入新元素的位置的索引(即队尾元素的后一个位置)。

初始状态下,队列为空,frontrear 都指向数组的起始位置(索引 0)。

  • 入队 enqueue:将新元素放入 data[rear],然后将 rear 指针向后移动一位 (rear++)。
  • 出队 dequeue:取出 data[front] 的元素,然后将 front 指针向后移动一位 (front++)。
  • 判空条件:当 front == rear 时,队列为空。

2.2 核心操作代码实现 (Java)

下面我们用 Java 来创建一个基于数组的顺序队列 ArrayQueue

2.2.1 初始化与成员变量

我们定义一个类,包含数组 data、容量 capacity、以及 frontrear 指针。

public class ArrayQueue<E> {private E[] data;private int front; // 队头指针,指向队头元素private int rear;  // 队尾指针,指向下一个可插入位置private int size;  // 队列中当前元素数量private int capacity; // 队列容量public ArrayQueue(int capacity) {this.capacity = capacity;// 不能直接 new E[capacity],需要类型转换this.data = (E[]) new Object[capacity];this.front = 0;this.rear = 0;this.size = 0;}public int getSize() {return size;}public boolean isEmpty() {return size == 0;}public boolean isFull() {// 注意:这里的判满条件在后面会发现问题return rear == capacity; }
}

2.2.2 入队操作 (enqueue)

入队操作需要检查队列是否已满。

// 在 ArrayQueue 类中添加/*** 入队操作* @param e 要入队的元素* @return 成功返回 true,失败(队列已满)返回 false*/
public boolean enqueue(E e) {// 检查队列是否已满 (基于 rear 指针判断)if (rear == capacity) {System.out.println("队列已满,无法入队!");return false;}// 将元素放入 rear 指向的位置data[rear] = e;// rear 指针后移rear++;// 元素数量加一size++;return true;
}

该操作的时间复杂度为 O(1)O(1)O(1)

2.2.3 出队操作 (dequeue)

出队操作需要检查队列是否为空。

// 在 ArrayQueue 类中添加/*** 出队操作* @return 返回队头元素,若队列为空则抛出异常*/
public E dequeue() {// 检查队列是否为空if (isEmpty()) {throw new IllegalStateException("队列为空,无法出队!");}// 获取队头元素E frontElement = data[front];// 将原队头位置置为 null,帮助垃圾回收 (GC)data[front] = null; // front 指针后移front++;// 元素数量减一size--;return frontElement;
}

该操作的时间复杂度也为 O(1)O(1)O(1)

2.2.4 其他辅助操作

// 在 ArrayQueue 类中添加/*** 查看队头元素* @return 返回队头元素,但不移除*/
public E peek() {if (isEmpty()) {throw new IllegalStateException("队列为空!");}return data[front];
}

三、顺序队列的痛点:“假溢出”问题

我们的 ArrayQueue 看起来运行良好,但它隐藏着一个致命缺陷。让我们通过一个操作序列来暴露它。

假设我们有一个容量为 4 的队列:

  1. 入队 A, B, C, Dfront=0, rear=4。数组填满。
  2. 出队 A, Bfront=2, rear=4。此时队列中有 C, D 两个元素。
  3. 尝试入队 E:此时 isFull() 条件 (rear == capacity) 为 true,系统报告“队列已满”。

问题来了:数组的前两个位置 (data[0]data[1]) 明明是空闲的,为什么却无法插入新元素?

3.1 什么是“假溢出”?

这种数组中明明还有可用空间,但因为队尾指针 rear 已经到达了数组末端而导致无法继续入队的现象,就被称为“假溢出”(False Overflow)

下面是这个过程的可视化:

数组 (容量=4)指针 (front, rear)初始: front=0, rear=0enqueue(A) ->> data[0]=Arear=1enqueue(B) ->> data[1]=Brear=2enqueue(C) ->> data[2]=Crear=3enqueue(D) ->> data[3]=Drear=4loop[连续入队]此时队列已满: [A, B, C, D]dequeue() ->> 返回A, data[0]变空front=1dequeue() ->> 返回B, data[1]变空front=2loop[连续出队]此时队列状态: [ , , C, D]front=2, rear=4尝试 enqueue(E)检查 rear (4) == capacity (4)判断为满,发生“假溢出”!数组 (容量=4)指针 (front, rear)

3.2 “假溢出”的成因分析

根本原因在于数组的索引是线性和固定的,而我们的 frontrear 指针只能单向递增。当 rear 到达终点后,它无法“回头”利用数组开头的空闲空间。出队操作只是移动 front 指针,逻辑上移除了元素,但物理上被“废弃”的空间无法被 rear 指针重新利用。

3.3 如何解决?(引出循环队列)

要解决“假溢出”问题,最经典的方法就是将数组的线性空间“掰弯”,变成一个环形空间。当 rearfront 指针移动到数组末尾时,让它能自动“绕”回到数组的开头。这种优化后的队列结构,就是我们下一篇文章将要深入探讨的——循环队列(Circular Queue)。它通过巧妙的模运算(%)来实现指针的循环移动,从而完美解决假溢出问题,大大提高数组空间的利用率。

四、队列的应用场景

队列作为一种基础数据结构,其 FIFO 的特性使其在计算机科学的各个领域都有着广泛的应用。

4.1 计算机系统中的队列

  • 操作系统任务调度:操作系统将待执行的进程放入一个就绪队列中,然后按照一定的策略(如先来先服务)从队列中取出进程分配 CPU 资源。
  • 打印机任务队列:当你发送多个打印请求时,这些请求会被放入一个打印队列中,打印机按照顺序逐一处理。
  • 消息队列(Message Queue, MQ):在分布式系统中,消息队列(如 RabbitMQ, Kafka)是核心组件,用于服务间的解耦、异步通信和流量削峰。生产者将消息放入队列,消费者从队列中取出消息进行处理。

4.2 算法中的应用:广度优先搜索 (BFS)

广度优先搜索(BFS)是一种非常重要的图和树的遍历算法,其核心就是借助队列来实现。BFS 从一个起始节点开始,逐层地向外扩展搜索。队列保证了同一层的节点被访问完毕后,才会进入下一层的节点,完美契合了“逐层”探索的需求。我们将在后续的图论章节中详细讲解。

4.3 日常生活中的模型

任何符合“先到先服务”原则的场景,都可以抽象为队列模型,例如:

  • 在线票务系统的排队抢票。
  • 银行或客服中心的客户等待队列。
  • 网络数据包在路由器中的转发处理。

五、总结

本文我们对队列(Queue)进行了系统的学习,以下是核心知识点回顾:

  1. 核心定义:队列是一种先进先出(FIFO)的线性数据结构,操作受限,只允许在队尾(Rear)入队,在队头(Front)出队
  2. 核心操作:主要包括 enqueue(入队)、dequeue(出队)、peek(查看队头)等。
  3. 顺序队列实现:使用数组配合两个指针(frontrear)来实现。入队和出队操作的平均时间复杂度均为 O(1)O(1)O(1),非常高效。
  4. 关键痛点:简单的顺序队列存在**“假溢出”**问题。即数组中仍有空位,但因 rear 指针已达末端而无法入队,导致空间浪费。
  5. 应用广泛:队列是操作系统调度、消息中间件、广度优先搜索(BFS)算法等众多应用场景的基石。

理解顺序队列及其“假溢出”的局限性,是学习更高级队列结构(如循环队列)的必要前提。在下一篇文章中,我们将着手解决这个痛点,敬请期待!


http://www.dtcms.com/a/324820.html

相关文章:

  • 端口扫描器用户使用手册 (EXE版)
  • JavaScript 变量:数据存储的核心机制
  • C++ 黑马 内存分配模型
  • 详解Windows(十六)——计划任务
  • Linux安装Jenkins-2.432,jdk17
  • Day11 原理篇
  • 华为防火墙配置指南【附实战案例】
  • python urllib模块怎么使用
  • 【软件测试】概念篇 — 详解
  • 广东省省考备考(第七十二天8.10)——言语理解与表达、判断推理(强化训练)
  • APISIX 路由优先级
  • SupChains团队:化学品制造商 ChampionX 供应链需求预测案例分享(十七)
  • 托福阅读记录
  • TypeScript 中的as const是什么?
  • 基于Actor-Critic策略的Atari中的pong_v3
  • 机器学习第八课之K-means聚类算法
  • 零基础学编程,编程从入门到精通系列教程,附:编程工具箱之公用事件的用法#零基础学编程从哪里开始#新手学编程先学什么#公用事件构件编程用法
  • Lua语言变量、函数、运算符、循环
  • 小学数学计算技巧全攻略
  • 攻击者瞄准加密技术的基础:智能合约
  • [网安工具] Web 漏洞扫描工具 —— GoBy · 使用手册
  • Video Lecture 8 Page Fault
  • 7、西门子PLC基础术语:数据单位、存储区域、寻址方式、字节序
  • LightGBM 与 GBDT 在机器学习中的性能与特点比较
  • element-ui el-progress在有小数的情况下,会换行显示。解决不换行的问题。
  • redis集群-本地环境
  • 【前端基础】14、CSS设置背景(background相关的)
  • 正则表达式常用语法参考
  • STM32H7 以太网配置引申的内存问题
  • A2A协议深度理解与实践