排好队,一个一个来:宫本武藏教你学队列(附各种队列源码)

文章目录

    • 前言:
    • 理解“队列”的正确姿势
    • 一个关于队列的小思考——请求处理
    • 队列的两大“护法”————顺序队列和链式队列
      • 数组实现的队列
      • 链表实现的队列
    • 循环队列
    • 关于开篇,你明白了吗?
    • 最后说一句

前言:

哈喽!欢迎来到黑洞晓威的博客!

上一次我们在这里聊了一下队列,现在,让我们再次翻开这个话题,继续探讨一下这个有趣的数据结构吧!
虽然队列看起来比较普通,但是它在实际应用中却 有着不可替代的作用。所以,无论是计算机系统中的任务调度,还是网络数据包的传输,队列都扮演着重要的角色。
接下来,我们将深入了解队列的应用、实现以及相关算法问题。让我们一起来暴打队列吧!

在这里插入图片描述

理解“队列”的正确姿势

王者荣耀中宫本武藏有这么一句台词——“想挑战的人排好队,一个一个来”。
这句台词可以很好地联系到数据结构中的队列。在数据结构中,队列就像是一群人在排队等待挑战宫本武藏,每个人都必须按照先来后到的原则,依次接受服务。当新的人加入队伍时,必须排在队尾,而队伍中的人只能按照先后顺序依次出队。

在这里插入图片描述

我们知道,栈只支持两个基本操作: 入栈push()和出栈pop()。队列跟栈非常相似,支持的操作也很有限,最基本的操作也是两个: 入队enqueue(),放一个数据到队列尾部; 出队dequeue(),从队列头部取一个元素。

在这里插入图片描述

所以,队列跟栈一样,也是一种 操作受限的线性表数据结构

队列的概念很好理解,基本操作也很容易掌握。作为一种非常基础的数据结构,队列的应用也非常广泛,比如排队、缓存、广度优先搜索等等。你准备好了吗?让我们一起来探索队列的奥秘吧!

一个关于队列的小思考——请求处理

我们知道,CPU资源是有限的,任务的处理速度与线程个数并不是线性正相关。相反,过多的线程反而会导致CPU频繁切换,处理性能下降。所以,线程池的大小一般都是综合考虑要处理任务的特点和硬件环境,来事先设置的。

当我们向固定大小的线程池中请求一个线程时,如果线程池中没有空闲资源了,这个时候线程池如何处理这个请求?是拒绝请求还是排队请求?各种处理策略又是怎么实现的呢?

实际上,这些问题并不复杂,其底层的数据结构就是我们今天要学的内容,队列(queue)。

队列的两大“护法”————顺序队列和链式队列

嘿,大佬们!我们现在知道了,队列跟栈一样,也是一种抽象的数据结构。它的特性很简单,就是先进先出,支持在队尾插入元素,在队头删除元素。但是,究竟

该如何实现一个队列呢?

让我们来看看吧!就像栈一样,队列也可以用数组来实现,这种实现方式叫做顺序队列。还可以用链表来实现,这种实现方式叫做链式队列。顺序队列和链式队列都有各自的优缺点,需要根据实际情况进行选择。所以,无论是顺序队列还是链式队列,都是我们实现队列的可选方案。现在,让我们先来看下基于数组的实现方法吧!

数组实现的队列

// 用数组实现的队列
public class ArrayQueue {// 数组:items,数组大小:nprivate String[] items;private int n = 0;// head表示队头下标,tail表示队尾下标private int head = 0;private int tail = 0;// 申请一个大小为capacity的数组public ArrayQueue(int capacity) {items = new String[capacity];n = capacity;}// 入队public boolean enqueue(String item) {// 如果tail == n 表示队列已经满了if (tail == n) return false;items[tail] = item;++tail;return true;}// 出队public String dequeue() {// 如果head == tail 表示队列为空if (head == tail) return null;// 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了String ret = items[head];++head;return ret;}
}

比起栈的数组实现,队列的数组实现稍微有点儿复杂,但是没关系。我稍微解释一下实现思路,你很容易就能明白了。

对于栈来说,我们只需要一个 栈顶指针 就可以了。但是队列需要两个指针:一个是head指针,指向队头;一个是tail指针,指向队尾。

你可以结合下面这张图来理解。当a、b、c、d依次入队之后,队列中的head指针指向下标为0的位置,tail指针指向下标为4的位置。

在这里插入图片描述

当我们调用两次出队操作之后,队列中head指针指向下标为2的位置,tail指针仍然指向下标为4的位置。

在这里插入图片描述

你肯定已经发现了,随着不停地进行入队、出队操作,head和tail都会持续往后移动。当tail移动到最右边,即使数组中还有空闲空间,也无法继续往队列中添加数据了。这个问题该如何解决呢?

你是否还记得,在数组那一节,我们也遇到过类似的问题,就是数组的删除操作会导致数组中的数据不连续。你还记得我们当时是怎么解决的吗?对,用 数据搬移!但是,每次进行出队操作都相当于删除数组下标为0的数据,要搬移整个队列中的数据,这样出队操作的时间复杂度就会从原来的O(1)变为O(n)。能不能优化一下呢?

实际上,我们在出队时可以不用搬移数据。如果没有空闲空间了,我们只需要在入队时,再集中触发一次数据的搬移操作。借助这个思想,出队函数dequeue()保持不变,我们稍加改造一下入队函数enqueue()的实现,就可以轻松解决刚才的问题了。下面是具体的代码:

   // 入队操作,将item放入队尾public boolean enqueue(String item) {// tail == n表示队列末尾没有空间了if (tail == n) {// tail ==n && head==0,表示整个队列都占满了if (head == 0) return false;// 数据搬移for (int i = head; i < tail; ++i) {items[i-head] = items[i];}// 搬移完之后重新更新head和tailtail -= head;head = 0;}items[tail] = item;++tail;return true;}

从代码中我们看到,当队列的tail指针移动到数组的最右边后,如果有新的数据入队,我们可以将head到tail之间的数据,整体搬移到数组中0到tail-head的位置。

在这里插入图片描述

这种实现思路中,出队操作的时间复杂度仍然是O(1),但入队操作的时间复杂度还是O(1)吗?你可以用我们第3节、第4节讲的算法复杂度分析方法,自己试着分析一下。

接下来,我们再来看下 基于链表的队列实现方法

链表实现的队列

基于链表的实现,我们同样需要两个指针:head指针和tail指针。它们分别指向链表的第一个结点和最后一个结点。如图所示,入队时,tail->next= new_node, tail = tail->next;出队时,head = head->next。我将具体的代码放到GitHub上,你可以自己试着实现一下,然后再去GitHub上跟我实现的代码对比下,看写得对不对。

首先,我们需要定义一个节点类,它包含一个值属性和一个指向下一个节点的指针属性:

public class Node<T> { // 这是一个泛型类,T表示节点的值可以是任何类型T value; // 存储节点的值Node<T> next; // 指向下一个节点的指针public Node(T value) { // 构造函数,用来创建新的节点this.value = value; // 设置节点的值this.next = null; // 初始时,指针为空}
}

接下来,我们定义一个队列类,它包含一个指向队列头部的指针属性和一个指向队列尾部的指针属性:

public class Queue<T> { // 这是一个泛型类,T表示队列的元素可以是任何类型Node<T> head; // 指向队列头部的指针Node<T> tail; // 指向队列尾部的指针public Queue() { // 构造函数,用来创建新的队列this.head = null; // 初始时,头部指针为空this.tail = null; // 初始时,尾部指针为空}
}

在队列类中,我们可以实现以下方法:

  1. enqueue(value):将一个元素添加到队列的尾部。
public void enqueue(T value) { // 将元素value添加到队列的尾部Node<T> newNode = new Node<>(value); // 创建一个新的节点if (tail == null) { // 如果队列为空head = newNode; // 将头部指针指向新节点tail = newNode; // 将尾部指针指向新节点} else { // 如果队列不为空tail.next = newNode; // 将当前尾部节点的指针指向新节点tail = newNode; // 将尾部指针指向新节点}
}
  1. dequeue():从队列的头部移除并返回一个元素。
public T dequeue() { // 从队列的头部移除并返回一个元素if (head == null) { // 如果队列为空return null; // 返回null}T value = head.value; // 获取队列头部节点的值head = head.next; // 将头部指针指向下一个节点if (head == null) { // 如果队列为空tail = null; // 将尾部指针也置为空}return value; // 返回队列头部节点的值
}
  1. peek():返回队列头部的元素,但不移除它。
public T peek() { // 返回队列头部的元素,但不移除它if (head == null) { // 如果队列为空return null; // 返回null}return head.value; // 返回队列头部节点的值
}
  1. isEmpty():检查队列是否为空。
public boolean isEmpty() { // 检查队列是否为空return head == null; // 如果头部指针为空,队列为空
}
  1. clear():清空队列。
public void clear() { // 清空队列head = null; // 将头部指针置为空tail = null; // 将尾部指针置为空
}

在这里插入图片描述

在这里偷偷告诉大家,基于链表的队列我懒得写了于是就交给了ChatGPT完成,说实话效果竟然出奇的好,大家在学习这些基础内容的时候也不妨善用ChatGPT,说必定你就能解锁一个优质的老师哈哈哈。

在这里插入图片描述

循环队列

我们刚才用数组来实现队列的时候,在tail==n时,会有数据搬移操作,这样入队操作性能就会受到影响。那有没有办法能够避免数据搬移呢?我们来看看循环队列的解决思路。

循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。我画了一张图,你可以直观地感受一下。

在这里插入图片描述

我们可以发现,图中这个队列的大小为8,当前head=4,tail=7。当有一个新的元素a入队时,我们放入下标为7的位置。但这个时候,我们并不把tail更新为8,而是将其在环中后移一位,到下标为0的位置。当再有一个元素b入队时,我们将b放入下标为0的位置,然后tail加1更新为1。所以,在a,b依次入队之后,循环队列中的元素就变成了下面的样子:

在这里插入图片描述

通过这样的方法,我们成功避免了数据搬移操作。看起来不难理解,但是循环队列的代码实现难度要比前面讲的非循环队列难多了。要想写出没有bug的循环队列的实现代码,我个人觉得,最关键的是, 确定好队空和队满的判定条件

在用数组实现的非循环队列中,队满的判断条件是tail == n,队空的判断条件是head == tail。那针对循环队列,如何判断队空和队满呢?

队列为空的判断条件仍然是head == tail。但队列满的判断条件就稍微有点复杂了。我画了一张队列满的图,你可以看一下,试着总结一下规律。

在这里插入图片描述

就像我图中画的队满的情况,tail=3,head=4,n=8,所以总结一下规律就是:(3+1)%8=4。多画几张队满的图,你就会发现,当队满时, (tail+1)%n=head

你有没有发现,当队列满时,图中的tail指向的位置实际上是没有存储数据的。所以,循环队列会浪费一个数组的存储空间。

Talk is cheap,如果还是没怎么理解,那就show you code吧。

public class CircularQueue {// 数组:items,数组大小:nprivate String[] items;private int n = 0;// head表示队头下标,tail表示队尾下标private int head = 0;private int tail = 0;// 申请一个大小为capacity的数组public CircularQueue(int capacity) {items = new String[capacity];n = capacity;}// 入队public boolean enqueue(String item) {// 队列满了if ((tail + 1) % n == head) return false;items[tail] = item;tail = (tail + 1) % n;return true;}// 出队public String dequeue() {// 如果head == tail 表示队列为空if (head == tail) return null;String ret = items[head];head = (head + 1) % n;return ret;}
}

关于开篇,你明白了吗?

队列的知识就讲完了,我们现在回过来看下开篇的问题。线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢?
在这里插入图片描述

我们一般有两种处理策略。

第一种是非阻塞的处理方式,直接拒绝任务请求;

另一种是阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。那如何存储排队的请求呢?

我们希望公平地处理每个排队的请求,先进者先服务,所以队列这种数据结构很适合来存储排队请求。我们前面说过,队列有基于链表和基于数组这两种实现方式。这两种实现方式对于排队请求又有什么区别呢?

基于链表的实现方式,可以实现一个支持无限排队的无界队列(unbounded queue),但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。

而基于数组实现的有界队列(bounded queue),队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说,就相对更加合理。
不过,设置一个合理的队列大小,也是非常有讲究的。队列太大导致等待的请求太多,队列太小会导致无法充分利用系统资源、发挥最大性能。

最后说一句

感谢大家的阅读,文章通过网络学习资源以及自己的学习过程整理出来,希望能帮助到大家。

才疏学浅,难免会有纰漏,如果你发现了错误的地方,可以提出来,我会对其加以修改。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分享几个线上副业!!

线上副业 有哪些可以在线上运作就能赚取生活费的方式&#xff1f; 我这个暑假没有去打暑假工&#xff0c;因为疫情原因&#xff0c;一直待在家里&#xff0c;没有收入就不能出去玩&#xff0c;不能买漂亮衣服&#xff0c;就开始了一系列的线上兼职寻找&#xff0c;到处碰壁&…

学会Python如何去变现?副业月收入10000+了解一下

自学 Python 之后如果不去公司上班&#xff0c;自己一个人可以通过此技能挣什么钱&#xff1f; 逆天的Python&#xff0c;只要你掌握了相关技术&#xff0c;就可以靠它赚钱&#xff0c;具体怎么赚&#xff0c;我们来看看一位小哥哥的回答。 以我差不多四年的 Python 使用经验…

悟空问答赚钱副业项目,操作的好可月入10000+

我不知道你是否做过这种项目。这也是自媒体。如果你没有&#xff0c;你可以试试。收入不错。 有人可能会说悟空问答已经过时了。事实上&#xff0c;悟空问答每天仍能挣300元。好吧&#xff0c;我们已经取得了经验&#xff0c;在这里我们也与大家分享。 基本上&#xff0c;每个…

ChatGPT:编写一个带UI界面的计算器

代码&#xff1a; import tkinter as tkclass Calculator:def __init__(self, master):self.master mastermaster.title("Calculator")self.total tk.StringVar()self.entered_number tk.StringVar()self.entered_number.set(0)self.total.set(0)self.entry tk.…

使用Java进行编曲

一、编曲部分 1.1一丢丢乐理知识 简单普及下乐理哈&#xff0c;这样便于读谱 钢琴谱一行分两个部分 上面一行用右手弹(主奏)&#xff1b; 下面一行用左手弹奏(伴奏)。 1.2 关于节奏 &#xff08;1&#xff09;、主奏与伴奏中支持输入的35个音符&#xff1a; 倍低音&#…

小米系列手机(包括红米,黑鲨)开启调试模式

1. 点击我的设备 进入设置主页面&#xff0c;点击我的设备&#xff0c;点击全部参数。 2. 点击MIUI版本 连续点击MIUI版本直到出现提示&#xff0c;开发者权限已开启。 3. 点击更多设置 返回设置&#xff0c;点击更多设置。 4. 查看信息 在更多设置中就能看到开发者选项。 …

小米手机超越苹果,成欧洲第二;马斯克特斯拉内部邮件:痛恨开会,少讲黑话;Spring 6.0 发布|极客头条...

「极客头条」—— 技术人员的新闻圈&#xff01; CSDN 的读者朋友们早上好哇&#xff0c;「极客头条」来啦&#xff0c;快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 一分钟速览新闻点&#…

MiPush四种推送对象

文档中心 推送对象目前支持四种&#xff1a;RegID、别名、userAccount、标签。 RegID&#xff1a;针对单一设备推送消息。应用调用MiPushClient类的静态方法registerPush注册小米推送服务&#xff0c;注册的结果将通过PushMessageReceiver继承类的onCommandResult方法和onRec…

小米正式宣布:这种手机以后买不到了…

开头先问大家一个问题&#xff0c;你的手机屏幕尺寸是多少&#xff1f; 还记得当初乔老爷子发布 iPhone 时&#xff0c;称 3.5 英寸是人手握持的最佳尺寸。 不过&#xff0c;当时苹果显然没有考虑到奥尼尔这样体格魁梧的人的使用感受... 3.5 英寸&#xff0c;4.0 英寸&#xf…

MIUI金凡回应用户反馈小米手机发热情况

本文转载自IT之家 IT之家 6 月 17 日消息 小米产品总监、MIUI 体验总负责人金凡近期称&#xff0c;已正式成立了“MIUI 先锋小组”&#xff0c;集中解决大家反馈的各类体验问题&#xff0c;做好首席客服小组。接下来会以报告的形式将工作进度发在小米社区中&#xff0c;请大家…

原来这样可以优雅地解决小米手机后台弹窗权限问题

/ 今日科技快讯 / 7月23日&#xff0c;据外媒报道&#xff0c;微软宣布将向总部位于美国旧金山的人工智能研究公司OpenAI投资10亿美元&#xff0c;为其云计算平台开发AI技术。 / 作者简介 / 本篇文章转载自nodzhang的博客&#xff0c;分享了他对于小米手机后台弹出界面…

时薪15美元的ChatGPT外包工人,干的都是苦力活

整理 | 朱珂欣 出品 | CSDN程序人生&#xff08;ID&#xff1a;coder_life&#xff09; 自 ChatGPT 去年 11 月发布以来&#xff0c;让不少打工人陷入担心失业的恐慌中&#xff0c;也解决了部分人的“就业问题”。 34 岁的 Alexej Savreux &#xff0c;就是其中之一。 作为 …

AutoGPT:全自动的人工智能助手

让 GPT-4 为你实现一切&#xff01; 随着人工智能技术的飞速发展&#xff0c;GPT-4 作为强大的人工智能语言模型成为了众多应用场景的核心。今天&#xff0c;我们将为你揭秘一款具有革命性意义的 GPT-4 应用——AutoGPT&#xff01;一款让你轻松操控 GPT-4&#xff0c;实现各种…

文心一言、GPT3.5及GPT4的应用测评对比

省时查报告-专业、及时、全面的行研报告库 省时查方案-专业、及时、全面的营销策划方案库 【免费下载】2023年2月份热门报告合集 最新亲测国内可用ChatGPT使用教程&#xff08;3分钟搞定&#xff09; ChatGPT团队背景研究报告 ChatGPT的发展历程、原理、技术架构及未来方向 Cha…

看New Bing回答世纪难题:女友和妈妈掉水里先救谁

1.女友和妈妈掉水里先救谁 今天好奇想看看New Bing怎么回答这种世纪难题 结果New Bing非常聪明&#xff0c;反手建议我不要直接回答这个问题&#xff0c;而是换个角度哄女朋友&#xff0c;带着点不甘心&#xff0c;我继续追问它 New Bing还是耍起了滑头&#xff0c;我开始怀疑…

“一天宕机三次”,为什么高并发这么难?

受访者 | 陈皓 作者 | 屠敏 出品 | 《新程序员》编辑部 高并发&#xff0c;并不是一个新鲜的话题&#xff0c;全互联网公司为之“费尽心思”多年&#xff0c;仍然无法完全逃脱卡顿、崩溃乃至宕机的宿命。 这不近日&#xff0c;一款名为“羊了个羊”的小程序游戏突然爆火&am…

美图终于等来AIGC的春天

作者&#xff5c;陈 妍 编辑&#xff5c;大 风 人类历史上&#xff0c;经历过三次科技颠覆时刻。 第一次是上世纪90年代&#xff0c;PC互联网的兴起&#xff0c;开始把全世界连接到一起。1995年&#xff0c;以雅虎为代表的企业&#xff0c;开创免费门户网站的互联网行业商业…

UML建模都有那些图(架构师必刷)

分析&回答 统一建模语言&#xff08;Unified Modeling Language&#xff0c;UML&#xff09;又称标准建模语言。 UML从考虑系统的不同角度出发&#xff0c;定义了用例图、类图、对象图、包图、状态图、活动图、序列图、通信图、构件图、部署图等10种图。下图中红字部分为…

SWAT 建模与案例应用

SWAT 建模与案例应用 一、模型简介 SWAT模型是美国农业部&#xff08;USDA&#xff09;的农业研究中心开发的分布式水文模型。 主要目的是为了预测在大流域复杂多变的土壤类型、土地利用方式和管理措施条件下&#xff0c;土地管理对水分、泥沙和化学物质的长期影响。 近年来…

UML建模工具Astah Pro教程

文章目录 前言一、下载安装Astah二、破解Astah总结 前言 UML建模工具Astah Pro 破解教程&#xff0c;拒绝盗版白嫖行为&#xff0c;只是本人囊中羞涩&#xff0c;想购买正版&#xff0c;但是心有余而力不足。所以此文章只做为技术学习交流&#xff0c;不用于任何商业用途。如果…