数据结构(Queue队列)

news2025/2/23 2:14:09

前言:

在计算机科学中,数据结构是构建高效算法和程序的基础,而队列(Queue)作为一种经典的线性数据结构,具有重要的地位。与栈(Stack)不同,队列遵循“先进先出”( FIFO(先进先出)原则,即最先加入队列的元素会最先被移除。这个特性使得队列在许多应用场景中都扮演着至关重要的角色。

队列在操作系统、计算机网络、消息发送、任务调度、串行编程等多个领域都有广泛的应用。例如,操作系统中的任务调度和进程管理通常使用队列来维护待处理的任务;在网络通信中中,数据包的传输顺序也依赖于队列的排列处理;在生产者-消费者模型中,队列串联了生产者和消费者之间的数据图,确保数据按顺序流动。

随着软件开发的深入,队列的应用逐渐从基础的任务调度划分出更加复杂的场景。特别是在多线程和并发编程中,线程之间的任务分配和通信经常借助队列来实现。支持基本的入队、出队操作,还可以通过一些高级功能,如优先队列、阻塞队列等,满足复杂的需求。

在Java中,队列得到了标准库的广泛支持,Queue接口和各种实现类(如LinkedList、、等)为开发者提供了丰富的选择。通过这些现成的实现,我们可以轻松地将队列引入到我们的中程序中,极大地提高了开发效率。PriorityQueueArrayDeque

在本文中,我们将深入探讨队列的基本概念、常见操作以及Java中的队列实现。我们将了解队列的工作原理,分析其在实际Spark中的应用场景,并通过代码示例演示如何使用队列解决实际问题。

无论你是初学者,还是有一定经验的开发者,掌握队列这一基础数据结构,能够为你在编写高效、可靠的软件系统时提供强大的支持。让我们一起走进队列的世界,探索它的奥秘与无限的可能。

1.队列讲解

1.什么是队列?

队列(Queue)是一种线性数据结构,它遵循先进先出(FIFO,First In, First Out)原则。 简单来说,队列就是一排排站着的人,最先排队的人最先得到服务每当有新的元素进入队列时,它总是从队列的尾部进入(入队),而从队列的头部(移除出队)。

形象的比喻:

  • 假设你正在排队买票。你站在队列的尾部,等待自己的顺序。第一个进入队列的人会最先被服务,这就是队列的工作方式。

2. 查询的基本操作

2.1队列的使用:

在Java中,Queue是个接口,底层是通过链表实现的。

队列是通过Queue接口实现的接口。Queue继承自Collection接口,提供了队列的基本操作。Java的队列有几种常用的实现方式:

  • LinkedListLinkedList类是一个端点链表实现,它实现了Queue接口,可以作为一个队列使用。
  • PriorityQueuePriorityQueue类实现了一个优先级排序,其中每个元素都有一个优先级,队列中的元素会根据优先级来排序。
  • ArrayDequeArrayDeque类实现了一个基于队列的双端队列,适用于需要在队列两端插入和删除元素的场景。

2.2 队列通常有四个最常用的基本操作:

  • 入队(enqueue):将一个元素添加到队列的尾部。
  • 出队(dequeue):移除并返回队列头部的元素。
  • 查看队列首元素(peek/front):返回队列首元素,但不删除它。
  • 判断队列是否为空(isEmpty):判断队列是否包含任何元素。
2.2.1 入队(Enqueue)

入队元素添加到队列的尾部。

例如,你有一个队列,初始状态为空。你加入队几个元素:

队列的尾部是3,新元素3被添加到队尾。

2.2.2 出队(Dequeue)

出队是移除并返回队列最前面的元素。每次出队的元素是队列中最先进入的元素(最前面的元素)。

例如:

出队后,队头的元素1被移除,队列中的元素顺序改变,新的队头元素是2

2.2.3 查看队列首元素(Peek)

查看队列首元素是检查队列头部的元素,不会将其从队列中删除。这对于您查看下一个要处理的元素时很有用。

例如:

此操作仅返回2,并不会改变队列的内容。

2.2.4 判断队列是否为空(isEmpty)

这是一个辅助操作,用于检查队列是否包含元素。如果队列为空,返回true,否则返回false

3. 队列应用程序场景

队列是一种非常有用的数据结构,广泛涵盖很多实际问题中。下面列举一些常见的应用场景:

3.1 任务调度

在操作系统中,队列用于任务调度。比如,多个进程(或线程)需要访问计算机的CPU,系统将它们按顺序队列排列,最先进入的进程先获得CPU资源。

3.2 打印任务

如果您有多个打印任务,打印机将按顺序处理它们。第一个加入队列的打印任务会首先被打印,第二个任务在第一个任务打印完成后开始打印,依此类推。

3.3 消息队列

消息队列(例如:RabbitMQ,Kafka)用于传递信息或任务。生产者将消息发送到队列中,消费者从队列中接收并处理消息。消息的传递也遵循先进先出的顺序。

3.4 广度优先搜索(BFS)

队列在图论的广度优先搜索(BFS)算法中广泛应用。在BFS中,我们使用队列来存储待访问的节点,确保节点按照层次顺序被访问。每访问一个节点,队列中的元素就会被处理,相邻节点加入队列。

3.5 实时数据流

队列常用于处理实时数据流。例如,在网络流量监控系统中,可以使用队列缓冲流入的数据包,按照顺序进行处理和转发。

5. 队列的优缺点

优点:
  • 先进先出(先进先出):顺序保证了基本的顺序,适合需要按顺序处理任务的场景。
  • 容易实现:队列的实现相对简单,且与其他数据结构(如栈、链表)有相似之处,容易理解。
缺点:
  • 操作架构:队列只能从头部删除元素,从尾部插入元素,无法像队列一样随机访问元素。
  • 存储限制:某些队列(如有限队列)可能有容量,需要考虑队列满时的处理方式。

2  队列模拟实现

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有 两种:顺序结构 和 链式结构。同学们思考下:队列的实现使用顺序结构还是链式结构好?

2.1使用双向链表来实现队列:

(一)节点类的定义:

    成员变量:

        public int val:用于存储节点所代表的值,这个值可以是任何你想要在链表中存放的整型数据,比如整数类型的元素值等,它是节点的关键数据部分。
        public ListNode prev:是一个指向当前节点前一个节点的指针(引用),用于构建双向链表中向后的关联,使得可以从当前节点回溯到前面的节点,这也是双向链表区别于单向链表的重要特性体现。
        ListNode next:同样是一个指针(引用),不过它指向当前节点的下一个节点,用于构建链表向前的连接顺序,通过这个指针可以依次访问后续节点。

链表头和尾指针声明:
head和last这两个变量都是ListNode类型,它们分别用于指向整个双向链表的头节点和尾节点。在双向链表的操作过程中,通过head可以从链表开头进行正向遍历(利用每个节点的next指针),而通过last可以从链表末尾进行反向遍历(利用每个节点的prev指针)。这两个指针方便了对整个链表的访问和操作,比如插入新节点到头部或者尾部、删除头部或者尾部节点等常见操作都依赖于它们准确地指向相应位置。

1.入队列offer

  • firstlast的更新

    • 空链表的处理:当链表为空时,first并且last都应指向新节点,这部分代码是正确的。
    • 非空链表的处理:如果链表不为空,当前的last节点的next指针指向新节点,并且新节点的prev指向当前last节点。最后更新last为新节点,这样就保证了新节点被正确地添加到了链表的尾部。
  • size++

    • size字段用于跟踪链表中节点的数量,每次插入节点时需要更新链表的大小,这里也正确地更新了size

2.出队列 poll (删除第一个节点)

  • 队列为空( first == null):

    • 直接返回-1,表示没有节点供应删除。
  • 队列有一个节点( first == last):

    • 这种情况下,删除节点后,链表就空了,所以firstlast都需要设置为null
  • 排队有多个节点

    • 保存当前头节点的值(即要返回的值)。
    • first更新为下一个节点first.next
    • 更新first.prevfirst.next引用,保证链表结构正确。

3.出队列peek(不删除第一个节点)

  • 队列为空

    • 当链表为空(first == null)时,返回-1作为标志值。这是合理的设计,表明没有元素可以查看。
  • 返回队头内容列表

    • 如果链表不为空,直接返回first.val,即链表的第一个节点的值。

4.返回队列的大小Size:

5.返回队列是否为空isEmpty:

如果first == null,说明链表为空,返回true;否则返回false

  6 代码展示

 public static class ListNode {
        ListNode next;
        ListNode prev;
        int val;

        ListNode(int val) {
         this.val = val;
        }
    ListNode first;
    ListNode last;
    int size = 0;

    // 入队列---向双向链表位置插入新节点
    public void offer(int val) {
        ListNode newNode = new ListNode(val);  // 创建新节点

        if (first == null) {  // 如果链表为空
            first = newNode;  // 将新节点作为链表的头节点
        } else {
            last.next = newNode;  // 将新节点添加到当前尾节点的后面
            newNode.prev = last;   // 设置新节点的前驱节点为当前尾节点
        }

        last = newNode;  // 更新尾节点为新节点
        size++;  // 更新链表的大小
    }
// 出队列---将双向链表第一个节点删除掉
        public int poll () {
            // 1. 队列为空
            // 2. 队列中只有一个元素----链表中只有一个节点---直接删除
            // 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
            int val = -1;  // 默认返回值
            // 1. 队列为空
            if (first == null) {
                return val;  // 返回 -1
            }
            // 2. 队列中只有一个元素
            else if (first == last) {
                val = first.val;  // 保存节点的值
                first = last = null;  // 清空链表
            }

            // 3. 队列中有多个元素
            else {
                val = first.val;       // 保存第一个节点的值
                first.prev.next = null;    // 将新的头节点的前驱节点的 `next` 设置为 `null`
                first = first.next;    // 更新头节点
                first.prev = null;     // 新头节点的前驱指针设置为 null
            }

            size--;  // 更新链表的大小
            return val;  // 返回被删除节点的值
        }
        // 获取队头元素---获取链表中第一个节点的值域
        public int peek () {
            if (first == null) {
                return -1;
            }
            return first.val;
        }
        public int size () {
            return size;
        }
        public Boolean isEmpty () {
            return first == null;
        }
    }

2.2循环队列:

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。 环形队列通常使用数组实现。

数组下标循环的小技巧

1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length

2. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length

3.如何区分空与满?

1. 通过添加 size 属性记录

2. 保留一个位置

3. 使用标记

 4.代码实现:

public class ArrayQueue {
    private int[] array;  // 存储队列元素的数组
    private int front;    // 队列的头部索引
    private int rear;     // 队列的尾部索引
    private int size;     // 当前队列中的元素个数
    private int capacity; // 队列的最大容量

    // 构造函数,初始化队列
    public ArrayQueue(int capacity) {
        this.capacity = capacity;
        this.array = new int[capacity]; // 创建固定大小的数组
        this.front = 0; // 队头初始位置
        this.rear = 0;  // 队尾初始位置
        this.size = 0;  // 队列初始化时没有元素
    }

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

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

    // 获取队列的大小
    public int size() {
        return size;
    }

    // 入队操作(添加元素到队列尾部)
    public void enqueue(int value) {
        if (isFull()) {
            System.out.println("队列已满,无法入队");
            return;
        }
        array[rear] = value;  // 将元素添加到队尾
        rear = (rear + 1) % capacity; // 循环队列,重新指向数组头部(当rear等于capacity时重新指向0)
        size++;  // 更新队列的大小
    }

    // 出队操作(移除队列头部的元素)
    public int dequeue() {
        if (isEmpty()) {
            System.out.println("队列为空,无法出队");
            return -1;  // 如果队列为空,返回-1
        }
        int value = array[front];  // 获取队头的元素
        front = (front + 1) % capacity; // 更新队头索引
        size--;  // 更新队列的大小
        return value;
    }

    // 查看队列头部的元素(不移除)
    public int peek() {
        if (isEmpty()) {
            System.out.println("队列为空");
            return -1;
        }
        return array[front];
    }

    // 打印队列中的元素
    public void display() {
        if (isEmpty()) {
            System.out.println("队列为空");
            return;
        }
        // 从队头到队尾打印所有元素
        for (int i = 0; i < size; i++) {
            System.out.print(array[(front + i) % capacity] + " ");
        }
        System.out.println();
    }
}

3.其他方法:

  • add(E e)offer(E e):用于向队列添加元素,add方法会在队列已满时抛出异常,而offer方法则返回false
  • remove()poll():从队列中移除并返回队列头部的元素。remove在队列为空时抛出异常,而poll在队列为空时返回null
  • element()peek():返回队列头部的元素但不删除它,element()在队列中为空时抛出异常,而peek()返回null
  • size()isEmpty()size()返回队列的元素个数,isEmpty()检查队列是否为空。
  • clear():清空队列中的所有元素。
  • toArray()toArray(T[] a):将队列转换为数据库,toArray返回Object[]类型的数据库,toArray(T[] a)返回指定类型的数据库。
  • contains(Object o):检查队列中是否包含某个元素。
  • clone():克隆队列并返回一个副本。

4.结语:

队列(Queue)作为一种经典的数据结构,凭借其先进先出的特性,在计算机科学中还是扮演着至关重要的角色。无论是在操作系统中的任务调度、网络中的数据包传输,在实际应用中的打印任务管理、线程池的任务处理等场景中,队列头在,执行了它替代的功能。

在本文中,我们深入探讨了Java中队列的定义、实现以及常用方法,理解了它的基本操作,如入队(enqueue)、出队(dequeue)、队列大小(size)、队头元素(peek) )等,同时还介绍了Java标准库中的Queue接口和常见的实现类,如LinkedListPriorityQueueArrayDeque。每一种实现都有其独特的优势和适用场景,开发者可以根据具体的需求选择最适合的队列类型。

队列不仅是学习数据结构的一个重要环节,也是现代编程中经常需要掌握的核心概念之一。通过对队列的深入理解和运用,程序员可以更好地设计高效的程序和算法,提高系统的性能和稳定性。

无论是基本的队列操作,还是通过队列实现复杂的业务逻辑,掌握队列的使用和优化技巧,都将为我们解决实际问题提供强大的工具。希望本文能够帮助你更好地理解和使用队列,并在实际开发中灵活应用这种数据结构。

随着技术的不断发展,我们也可以探索队列在矩阵编程、多元系统中的应用,深入研究线程安全队列、阻塞队列等更复杂的队列类型。在未来的工作和学习中,队列依然坚定将是我们编程之旅中夹具的一部分。

队列看似简单,却在复杂系统的设计中发挥着巨大的作用。它还是数据流转的关键,合理使用队列,破坏我们带来高效、可靠的程序结构。无论是单机程序队列系统,掌握这一基础数据结构,将使您在编程的道路上更进一步,成为更强大的开发者。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2258972.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EDA - Spring Boot构建基于事件驱动的消息系统

文章目录 概述事件驱动架构的基本概念工程结构Code创建事件和事件处理器创建事件总线创建消息通道和发送逻辑创建事件处理器消息持久化创建消息发送事件配置 Spring Boot 启动类测试消息消费运行项目 概述 在微服务架构和大规模分布式系统中&#xff0c;事件驱动架构&#xff…

仿iOS日历、飞书日历、Google日历的日模式

仿iOS日历、飞书日历、Google日历的日模式&#xff0c;24H内事件可自由上下拖动、自由拉伸。 以下是效果图&#xff1a; 具体实现比较简单&#xff0c;代码如下&#xff1a; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color;…

软考高级架构 - 10.5 软件架构演化评估方法

10.4 软件架构演化原则总结 本节提出了18条架构演化的核心原则&#xff0c;并为每条原则设计了简单而有效的度量方法&#xff0c;用于从系统整体层面提供实用信息&#xff0c;帮助评估和指导架构演化。 演化成本控制&#xff1a;成本小于重新开发成本&#xff0c;经济高效。进…

DocFlow票据AI自动化处理工具:出色的文档解析+抽取能力,提升企业文档数字化管理效能

目录 财务应付 金融信贷业务 近期&#xff0c;DocFlow票据自动化产品正式上线。DocFlow是一款票据AI自动化处理工具&#xff0c;支持不同版式单据智能分类扩展&#xff0c;可选功能插件配置流程&#xff0c;满足多样业务场景。 随着全球化与信息化进程&#xff0c;企业的文件…

C# 探险之旅:第二节 - 定义变量与变量赋值

欢迎再次踏上我们的C#学习之旅。今天&#xff0c;我们要聊一个超级重要又好玩的话题——定义变量与变量赋值。想象一下&#xff0c;你正站在一个魔法森林里&#xff0c;手里拿着一本空白的魔法书&#xff08;其实就是你的代码编辑器&#xff09;&#xff0c;准备记录下各种神奇…

有道云笔记批量导出

前言 最近使用有道云笔记遇到打开过慢&#xff0c;导致笔记丢失&#xff0c;需要会员才能找回之前笔记问题。 决定改用思源&#xff0c;程序中的格式比较难于通过复制保留&#xff0c;即使导出成word 或者pdf&#xff0c;需要一个专门工具导出成Markdown格式&#xff0c;批量…

离线无网环境中基于OpenEuler的everything ISO安装软件

文章目录 1.创建挂载点 2.挂载 ISO 文件: 3.配置 YUM 源 4.清理 YUM 缓存并生成新的缓存: 5.使用 YUM 安装软件包 要在 OpenEuler 系统中挂载ISO &#xff08;下载地址&#xff1a;https://repo.openeuler.openatom.cn/openEuler-20.03-LTS/ISO/x86_64/&#xff09;并使用…

2024最新树莓派4b安装ubuntu20.04.5-server版本全流程解决方案:从烧录到配置桌面到联网!!!

准备工作 硬件工具 树莓派4b&#xff0c;32GSD卡&#xff0c;读卡器 软件工具 ubuntu20.04.5镜像&#xff0c;SD卡格式化工具&#xff0c;烧录软件&#xff0c;远程连接工具。 下面是我通过百度网盘分享的文件&#xff1a;树莓派4bubuntu20.04链接&#xff1a;https://pan…

STM32 OLED屏幕驱动详解

一、介绍 OLED是有机发光二极管&#xff0c;又称为有机电激光显示&#xff08;Organic Electroluminescence Display&#xff0c; OLED&#xff09;。OLED由于同时具备自发光&#xff0c;不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广…

商业银行基于容器云的分布式数据库架构设计与创新实践

导读 本文介绍了某商业银行基于 TiDB 和 Kubernetes(简称 K8s) 构建的云化分布式数据库平台&#xff0c;重点解决了传统私有部署模式下的高成本、低资源利用率及运维复杂等问题。 通过引入 TiDB Operator 自动化管理与容器化技术&#xff0c;银行能够实现多个业务系统的高可用…

项目组件框架介绍[etcd]

文章目录 前言etcd安装Ubuntu 上通过包管理器安装通过源码安装配置 客户端开发包开发包的安装接口介绍添加一个键值对获取一个键值对租约保活机制监听 封装服务注册与发现服务注册服务发现 前言 Etcd 是一个 golang 编写的分布式、高可用的一致性键值存储系统&#xff0c;用于配…

网页爬虫技术全解析:从基础到实战

引言 在当今信息爆炸的时代&#xff0c;互联网上的数据量每天都在以惊人的速度增长。网页爬虫&#xff08;Web Scraping&#xff09;&#xff0c;作为数据采集的重要手段之一&#xff0c;已经成为数据科学家、研究人员和开发者不可或缺的工具。本文将全面解析网页爬虫技术&…

Jmeter如何对UDP协议进行测试?

Jmeter如何对UDP协议进行测试&#xff1f; 1 jmeter-plugins安装2 UDP-Protocol Support安装3 UDP协议测试 1 jmeter-plugins安装 jmeter-plugins是Jmeter的插件管理器&#xff1b;可以组织和管理Jmeter的所有插件&#xff1b;直接进入到如下页面&#xff0c;选择如图的选项进…

计算机网络之网络层超详细讲解

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络之网络层超详细讲解 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; …

微信小程序:实现节点进度条的效果;正在完成的节点有动态循环效果;横向,纵向排列

参考说明 微信小程序实现流程进度功能 - 知乎 上面的为一个节点进度条的例子&#xff0c;但并不完整&#xff0c;根据上述代码&#xff0c;进行修改完善&#xff0c;实现其效果 横向效果 代码 wxml <view classorder_process><view classprocess_wrap wx:for&quo…

如何不重启修改K8S containerd容器的内存限制(Cgroup方法)

1. 使用crictl 查看容器ID crictl ps2. 查看Cgroup位置 crictl inspect 容器ID3. 到容器Cgroup的目录下 使用上个命令就能找到CgroupPath 4 . 到cgroup目录下 正确目录是 : /sys/fs/cgroup/memory/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf68e18…

海康威视摄像头RTSP使用nginx推流到服务器直播教程

思路&#xff1a; 之前2020年在本科的时候&#xff0c;由于项目的需求需要将海康威视的摄像头使用推流服务器到网页进行直播。这里将自己半个月琢磨出来的步骤给大家发一些。切勿转载&#xff01;&#xff01;&#xff01;&#xff01; 使用网络摄像头中的rtsp协议---------通…

智简模型,边缘智能:AI 轻量化与边缘计算的最佳实践

文章目录 摘要引言模型轻量化与优化方法模型量化模型剪枝知识蒸馏合理使用边缘计算硬件 轻量化图像分类实战1. 模型量化2. 知识蒸馏3. 学生模型的创建与训练 QA环节总结参考资料 摘要 边缘计算与 AI 模型的结合&#xff0c;能够在资源受限的环境中提供实时智能服务。通过模型轻…

flink sink kafka的事务提交现象猜想

现象 查看flink源码时 sink kafka有事务提交机制&#xff0c;查看源码发现是使用两阶段提交策略&#xff0c;而事务提交是checkpoint完成后才执行&#xff0c;那么如果checkpoint设置间隔时间比较长时&#xff0c;事务未提交之前&#xff0c;后端应该消费不到数据&#xff0c…

推送(push)项目到gitlab

文章目录 1、git init1.1、在当前目录中显示隐藏文件&#xff1a;1.2、查看已有的远程仓库1.3、确保你的本地机器已经生成了 SSH 密钥&#xff1a;1.4、将生成的公钥文件&#xff08;通常位于 ~/.ssh/id_rsa.pub&#xff09;复制到 GitLab 的 SSH 设置中&#xff1a;1.5、测试 …