《数据结构与算法之美》学习笔记五之队列

news2024/12/27 12:24:00
前情提要:上一章学习了栈相关的知识,主要有下面的内容:
  • 栈操作的时间复杂度,对于顺序栈,入栈时如果栈的空间不够涉及到数据搬移,此时使用摊还分析法,将数据搬移的耗时均摊到不需要搬移数据的入栈操作中,均摊时间复杂度等于最好情况时间复杂度 O(1)
  • 栈在函数调用中的应用,内存给每一个线程都分配了一块独立的内存空间,这些内存空间被组织成“栈”的形式,用来存储函数调用时的临时变量,当进入一个函数时,会将这个函数作为一个栈帧入栈,当函数执行完毕时,会将对应的栈帧出栈。
  • 栈在表达式中的应用,对于加减乘除等等数学表达式的运算,计算机理解起来是很困难的,需要使用栈来辅助。需要一个运算符栈和一个操作数栈,遍历表达式,当遇到操作数,就压入操作数栈,当遇到运算符,则需要和运算符栈的栈顶运算符比较优先级,如果栈顶元素优先级高,如果当前运算符优先级更高,就压入运算运算符栈,继续下次对比;
  • 如何使用栈实现浏览器的前进后退功能?和计算表达式的值有点类似,也是需要两个栈,当访问新页面的时候,把页面压入栈A;当点击后退时,取出栈A的栈顶元素,压入栈B;当点击前进时,从栈B中取出栈顶元素,压入栈A;如果要访问新页面,就需要清除栈B

这一章继续来学习队列
队列比栈复杂那么一丢丢

(一)队列基本概念

队列的基本概念很好理解,就类似于买票排队,先来的先买,后来的排到队尾,也就是咱们经常听到的“先进先出”
队列与栈类似,也只支持两种操作:“入队” 和 “出队”。“入队” 就是新增一个元素到队列的末尾,“出队” 就是从队列的头部取出一个元素。
在这里插入图片描述
所以队列和栈一样,是一种操作受限的线性表数据结构。

(二)顺序队列和链式队列

跟栈一样,队列也可以用数组和链表实现。用数组实现的叫顺序队列,用链表实现的叫链式队列
下面是使用 Java 语言写的队列的数组实现

// 用数组实现的队列
public class ArrayQueue {
  // 数组:items,数组大小:n
  private 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 指针都会往后移动,如果这两个指针重合,即使数组中还有位置,也没办法再进行入队操作了。
回想一下数组那一章节,删除一个元素,造成数组空间不连续,后续再往数组中增加元素的时候,这个空出来的空间是没有办法利用的。此时我们怎么解决数据不连续的问题呢?对,就是使用数据搬移
每次的出队操作都相当于删除了索引为0的元素。但是我们并不需要在每次出队的时候都进行数据搬移,这样子会导致出队的时间复杂度变为 O(n),只需要在入队时,发现没有空闲空间的时候,进行数据搬移。
这样子的话,队列的出队函数并不需要修改,入队函数需要修改一下下

// 入队操作,将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和tail
    tail -= head;
    head = 0;
  }
  
  items[tail] = item;
  ++tail;
  return true;
}

从代码中可以看到,当队列的 tail 指针移动到最右边后,再往队列中添加元素时,就会将 head 指针到 tail 指针之间的数据往前搬移,从索引为0开始,到 tail - head 结束
在这里插入图片描述
在这种实现思路下,出队操作的时间复杂度仍然是 O(1)。入队操作,我们之前在数组那一章节分析过,均摊时间复杂度就等于最好情况时间复杂度,也是 O(1)。
接下来我们再来看一下基于链表的队列实现方式。
基于链表的实现,我们同样需要两个指针,head 指针指向链表的第一个结点,tail 指针指向链表的最后一个结点。如下图所示,
入队时,tail.next = newNode;tail = tail.next
出队时,head = head.next

在这里插入图片描述

(三)循环队列

循环队列,顾名思义,就是原来的拉直的队列首尾相连成一个圈圈
在这里插入图片描述
我们可以发现,图中的队列大小为8,head指针指向 4 的位置,tail 指针指向 7 的位置。
当有一个新元素入队的时候,新元素会放在 7 的位置,tail 指针并不需要更新为 8 ,而是需要到 0 的位置,同样的,再次新增时,tail 指针继续往前移动,到 1 的位置。新增两个元素的状态如下图所示:
在这里插入图片描述
通过这种方式,在队列空间满之前,都不需要进行数据搬移操作。
但是循环队列的实现,相比于非循环队列,会复杂一些。关键在于两个状态的确定,一个是队列为空的状态,一个是队列满员的状态。
在非循环的队列中,队列为空的判断标准是 head == tail ,队列满员的判断标准是 tail == n
在循环队列中,队列为空的判断条件依然是 head == tail,队列满员的状态如下图所示
在这里插入图片描述
队列满员的判断条件,可以通过多🖼几次图总结出来,是 (tail+1)%n=head。当队列满的时候,tail 的位置是没有存储数据的,这会造成一丢丢内存的浪费。

public class CircularQueue {
  // 数组:items,数组大小:n
  private 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 = (head + 1) % n;
    return ret;
  }
}

(四)阻塞队列

阻塞队列其实就是在队列的基础上增加了阻塞操作。当队列为空的时候,从对头取数据的操作就会被阻塞,等到队列中有数据的时候,在从队头取出数据并返回;入队操作也一样,当队列满的时候,入队操作会被阻塞,等到队列中有空位的时候才会执行插入操作,并返回。
在这里插入图片描述

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

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

相关文章

DeFi强势回归:新一轮DeFi牛市即将到来?

自2020年夏天的“DeFi之夏”以来&#xff0c;去中心化金融&#xff08;DeFi&#xff09;一直是加密行业的关键组成部分。“DeFi之夏”不仅将去中心化金融概念带入了实践&#xff0c;而且极大地推动了DeFi协议和应用的爆发式增长。尽管之后的市场经历了周期性的调整&#xff0c;…

【C++类的设计】题目(二):设计圆柱Column类

题目&#xff1a;设一个用于处理圆柱体的类Column&#xff0c;要求如下 (1)类中包含成员有&#xff1a;表示圆柱体底面半径的私有数据成员r&#xff0c;表示圆柱体高的私有数据成员h&#xff1b;构造对象时为私有数据成员赋值的构造函数&#xff0c;用于计算圆柱体表面积的函数…

OpenFeign使用详解

什么是OpenFeign&#xff1f; OpenFeign 是一个声明式的 HTTP 客户端&#xff0c;旨在简化微服务架构中不同服务之间的 HTTP 调用。它通过集成 Ribbon 实现了客户端负载均衡&#xff0c;并且能够与 Eureka、Consul 等服务发现组件无缝对接。使用 OpenFeign&#xff0c;开发者只…

如何使用ssm实现基于java web的防疫工作志愿者服务平台的设计与实现

TOC ssm693基于java web的防疫工作志愿者服务平台的设计与实现jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进…

Focalboard开源项目管理系统本地Windows部署与远程访问协同办公

文章目录 前言1. 使用Docker本地部署Focalboard1.1 在Windows中安装 Docker1.2 使用Docker部署Focalboard 2. 安装Cpolar内网穿透工具3. 实现公网访问Focalboard4. 固定Focalboard公网地址 &#x1f4a1; 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&am…

Redis 篇-深入了解在 Linux 的 Redis 网络模型结构及其流程(阻塞 IO、非阻塞 IO、IO 多路复用、异步 IO、信号驱动 IO)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 用户空间与内核空间概述 2.0 Redis 网络模型 2.1 Redis 网络模型 - 阻塞 IO 2.2 Redis 网络模型 - 非阻塞 IO 2.3 Redis 网络模型 - IO 多路复用 2.3.1 IO 多路复…

vscode【实用插件】Markdown Preview Enhanced 预览 .md 文件

安装 在 vscode 插件市场的搜索 Markdown Preview Enhanced点安装 使用 用 vscode 打开任意 .md 文件右键快捷菜单 最终效果 可打开导航目录

liunxcentos7下 跟目录空间不足docker load镜像报错空间不足

前两天在公司&#xff0c;做jenkins流水线项目&#xff0c;然后把项目放到docker容器里面运行&#xff0c;就在我把镜像打好包的时候正准备往服务器里面导入镜像的时候报错&#xff1a;如图所示 这时发现自己的根目录空间不足。 解决办法&#xff1a;重新加一块磁盘将磁盘挂载…

Qemu开发ARM篇-5、buildroot制作根文件系统并挂载启动

文章目录 1、 buildroot源码获取2、buildroot配置3、buildroot编译4、挂载根文件系统 在上一篇 Qemu开发ARM篇-4、kernel交叉编译运行演示中&#xff0c;我们编译了kernel&#xff0c;并在qemu上进行了运行&#xff0c;但到最后&#xff0c;在挂载根文件系统时候&#xff0c;挂…

基于 Redis 实现滑动窗口的限流

⏳ 限流场景&#xff1a;突发流量&#xff0c;恶意流量&#xff0c;业务本身需要 基于 Redis 实现滑动窗口的限流是一种常见且高效的做法。Redis 是一种内存数据库&#xff0c;具有高性能和支持原子操作的特点&#xff0c;非常适合用来实现限流功能。下面是一个使用 Redis 实现…

谷歌浏览器如何把常用的网址创建快捷方式到电脑桌面?

1、打开想要创建快捷方式的网页之后&#xff0c;点击谷歌浏览器右上角的【三个点】 2、选择【保存并分享】&#xff0c;再选择【创建快捷方式】 3、之后在浏览器上方弹出的框中&#xff0c;重新命名快捷方式。 然后&#xff0c;点击【创建】 4、之后&#xff0c;即可在电…

手机解压软件加密指南:让文件更安全

在数字化时代&#xff0c;文件加密对于保护个人隐私和敏感信息的重要性不言而喻。随着互联网的飞速发展&#xff0c;我们的生活和工作越来越依赖于数字设备和网络。 然而&#xff0c;这也带来了一系列的安全风险&#xff0c;如黑客攻击、数据泄露等。文件加密技术成为了保护我…

mac m1 electron生产环境使用prisma,sqlite

最近在用electron开发一个适合自己的小应用&#xff0c;技术选型中使用prisma和sqlite在进行数据存储&#xff0c;写这篇文章的目的就是用来记录下遇到的一些问题。 开发环境使用prisma 1、开发环境使用prisma非常的简单&#xff0c;只需要按照教程安装prisma&#xff0c;然后…

vue嵌套路由刷新页面空白问题

问题描述 在vue项目开发中遇到这样一个问题&#xff0c;在history模式下通过页面点击路由跳转可以打开页面&#xff0c;但是在当前页面刷新就空白了&#xff0c;如下&#xff1a; 点击路由跳转页面是有的 刷新页面就空白 代码 {path: "/home",name: "home&qu…

TreeMap源码详解

优质博文&#xff1a;IT-BLOG-CN 背景&#xff1a;昨天有人问我&#xff0c;他想将Map中的Key按照顺序进行遍历&#xff0c;我说直接使用keySet方法获取到Set集合&#xff0c;因为它是集成Collection接口&#xff0c;所以包含了sort方法后遍历取value值即可。但当看到TreeMap的…

差旅报销的数智化转型 以分贝通为例

企业差旅报销的数智化转型之所以势在必行&#xff0c;源于传统差旅报销方式在效率、合规性和成本控制等方面存在严重不足。作为服务企业的一体化差旅报销管理平台&#xff0c;分贝通结合数千家合作伙伴的实际案例为企业提供定制化的差旅报销数智化解决方案&#xff0c;帮助企业…

【Python报错已解决】AttributeError: ‘tuple‘ object has no attribute ‘log_softmax‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

CentOS 7 中安装 docker 环境

作者&#xff1a;程序那点事儿 日期&#xff1a;2023/02/15 02:31 官网地址 官网文档 docker三种网络模式 Docker CE 支持 64 位版本 CentOS 7&#xff0c;并且要求内核版本不低于 3.10&#xff0c; CentOS 7 满足最低内核的要求。 Docker 分为 CE 和 EE 两大版本。CE 即社区…

前端开发必备:实用Tool封装工具类方法大全

程序员必备宝典网站https://tmxkj.top/#/ 1.判断空值 /*** 判断是否是空值*/ export function isEmpty(value) {if (typeof value string) {return value.length 0 || value "";} else if (typeof value number) {return value 0;} else if (Array.isArray(va…