数据结构与算法—搞懂队列

csdn专栏:数据结构与算法

前言

栈和队列是一对紧密相关的数据结构。之前已经介绍过栈(它遵循后进先出的原则),栈的机制相对简单,就像你进入一个狭窄的山洞,山洞只有一个出入口,因此你只能按照后进先出的顺序离开。这意味着最后进入山洞的人会最先离开,而先进入的人需要等待。这就是栈的工作原理。

而队列就好比是一个隧道,人们按照先来后到的顺序排队前进,而最早排队的人会首先通过隧道。队列的运作方式被称为先入先出,即先进入队列的元素将会先离开。

栈是一种喜新厌旧的数据结构,新数据到来时,会处理新数据,而老数据将被推迟处理。因此栈的一些使用场景可能会遇到一些饥饿的情况。

队列则是一种公平的数据结构,它按照先后顺序处理数据,非常注重顺序性。因此,队列在程序设计、中间件等领域都得到了广泛应用,例如消息队列、FIFO磁盘调度、二叉树的层序遍历、BFS搜索等等。

队列的核心理念可以用简单的一句话来表达:先进先出

队列是一种特殊的线性表,其特殊之处在于只允许在表的前端(队头)进行删除操作,而在表的后端(队尾)进行插入操作。和栈一样,队列也是一种操作受限制的线性表。在队列中,进行插入操作的一端称为队尾,而进行删除操作的一端称为队头。

在学习队列前,最好先了解顺序表的基本操作和栈的数据结构。这样可以更好地理解队列的概念和工作原理,并提高学习效果。

队列介绍

我们设计队列时候可以选择一个标准:

队头front: 删除数据的一端。

队尾rear: 插入数据的一端。

对于数组,从数组后面插入更容易,数组前面插入较困难,所以一般用数组实现的队列队头在数组前面,队尾在数组后面;而对于链表,插入删除在两头分别进行那么头部(前面)删除尾部插入最方便的选择。

image-20231106212743098

实现一个队列的接口:

public interface Queue<T> {
    // 向队列尾部插入元素,如果成功插入则返回 true,否则返回 false
    boolean enQueue(T item);

    // 从队列头部删除一个元素,如果成功删除则返回 true,否则返回 false
    boolean deQueue();

    // 返回队列头部的元素,如果队列为空,返回 null
    T peek();

    // 检查队列是否为空
    boolean isEmpty();

    // 返回队列的大小
    int size();
}

循环队列(数组实现)

普通队列并不能满足使用需求,因为用数组实现的普通队列不论是入队还是出队都往一个方向操作,这样会导致很快到达数组的末尾。

为了解决普通队列资源利用率很低的问题,可以使用数组实现一个循环队列提高数组的利用率,具体的实现方式是对每个位置操作时候对实际地址取余这样尾和首就实现了逻辑上的连续。

实现循环队列,大致知道其原理,就要针对一些细节进行推敲以及定义,避免模糊。

front:表示队头,头位置元素出,定义array[front]为队列第一个元素位置。

rear:表示队尾,尾位置用来插入,定义array[tail]为队列最后一个元素位置。

image-20231106231314500

初始化:刚开始由于是空的,front为0,rear为-1,不过怎么样定义front和rear并不位于,需要处理好入队、出队、判空、判满的逻辑即可。

入队enQueue:队不满,先队尾逻辑上后移一位rear=(rear + 1) % arrLength;然后赋值,size++

出队deQueue:队不空,队头逻辑上后移一位,front=(front + 1)% arrLength;然后size–

这里出队入队指标相加如果遇到最后需要转到头位置,这里直接+1求余找到位置(相比判断是否在最后更加简洁),其中arrLength是数组实际大小。

取队头peek:队不空,返回array[front]

是否为空:size是否为0

具体实现:

public class ArrayQueue<T> implements Queue<T> {
    private T[] array;
    private int front; // 指向队列头部
    private int rear; // 指向队列尾部
    private int size;

    public ArrayQueue(int capacity) {
        array = (T[]) new Object[capacity];
        front = 0;
        rear = -1;
        size = 0;
    }

    @Override
    public boolean enQueue(T item) {
        if (size == array.length) {
            // 队列已满
            return false;
        }
        rear = (rear + 1) % array.length; // 循环队列,计算新的尾部位置
        array[rear] = item;
        size++;
        return true;
    }

    @Override
    public boolean deQueue() {
        if (isEmpty()) {
            // 队列为空
            return false;
        }
        front = (front + 1) % array.length; // 循环队列,移动头部指针
        size--;
        return true;
    }

    @Override
    public T peek() {
        if (isEmpty()) {
            return null;
        }
        return array[front];
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public int size() {
        return size;
    }
}

循环队列(链表实现)

我们知道队列是先进先出的,对于链表,我们能采用单链表尽量采用单链表,能方便尽量方便,同时还要兼顾效率。使用链表大概有两个实现方案:

方案一 如果队列头设在链表尾,队列尾设在链表头。那么队尾进队插入在链表头部插入没问题,容易实现,但是如果队头删除在链表尾部进行,如果不设置尾指针要遍历到队尾,但是设置尾指针删除需要将它前驱节点需要双向链表,都挺麻烦的。

方案二如果队列头设在链表头,队列尾设在链表尾,那么队尾进队插入在链表尾部插入没问题(用尾指针可以直接指向next),容易实现,如果队头删除在链表头部进行也很容易,就是我们前面常说的头节点删除节点

所以我们最终采取的是方案二,其中链表的头部表示队头,链表的尾部表示队尾。入队操作在队尾插入元素,出队操作在队头删除元素,通过维护 headtail 指针来管理队列。希望这个示例满足你的需求。

主要操作为:

初始化:head和tail均为null,size为0;

入队

  • 创建新节点

  • 如果队列为空(tail为null),head和tail都指向新节点

  • 如果队列不为空,tail的next指向新节点,tail指向新节点

  • size++

image-20231107002738965

出队

  • 如果不为空,移除链表头节点
  • head指向head后面(head.next)表示删除
  • size–
  • 如果head为null,tail也设为null(此时表示队列已经空了)

image-20231107001204876

实现代码:

public class LinkedQueue<T> implements Queue<T> {
    private Node<T> head; // 队列头部
    private Node<T> tail; // 队列尾部
    private int size;

    public LinkedQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public boolean enQueue(T item) {
        Node<T> newNode = new Node<>(item);
        if (isEmpty()) {
            head = newNode;
            tail = newNode;
        } else {
            tail.next = newNode; // 将新元素添加到队列尾部
            tail = newNode; // 更新队列尾部指针 也可携程tail=tail.next
        }
        size++;
        return true;
    }

    @Override
    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        head = head.next; // 移除队列头部的元素
        size--;
        if (head == null) {
            // 如果队列为空,将尾部也设置为null
            tail = null;
        }
        return true;
    }

    @Override
    public T peek() {
        if (isEmpty()) {
            return null;
        }
        return head.data; // 返回队列头部的元素数据
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public int size() {
        return size;
    }

    private static class Node<T> {
        T data;
        Node<T> next;

        Node(T data) {
            this.data = data;
            this.next = null;
        }
    }
}

双向队列(加餐)

设计实现双端队列,其实你经常使用的ArrayDeque就是一个经典的双向队列,其基于数组实现,效率非常高。我们这里实现的双向队列模板基于力扣641 设计循环双端队列 。
你的实现需要支持以下操作:

  • MyCircularDeque(k):构造函数,双端队列的大小为k。
  • insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
  • insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
  • deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
  • deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
  • getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
  • getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
  • isEmpty():检查双端队列是否为空。
  • isFull():检查双端队列是否满了。

其实有了上面的基础,实现一个双端队列非常容易,有很多操作和单端的循环队列是一致的,只有多了一个队头插入队尾删除的操作,两个操作分别简单的分析一下:

队头插入:队友front下标位置本身是有值的,所以要将front退后一位然后再赋值,不过要考虑是否为满或者数组越界情况。

队尾删除:只需要rear位置减1,同时也要考虑是否为空和越界情况。

具体实现代码:

public class MyCircularDeque {
    private int data[];// 数组容器
    private int front;// 头
    private int rear;// 尾
    private int maxsize;// 最大长度

    /*初始化 最大大小为k */
    public MyCircularDeque(int k) {
        data = new int[k + 1];
        front = 0;
        rear = 0;
        maxsize = k + 1;
    }

    /**
     * 头部插入
     */
    public boolean insertFront(int value) {
        if (isFull()) {
            return false;
        } else {
            front = (front + maxsize - 1) % maxsize;
            data[front] = value;
        }
        return true;
    }

    /**
     * 尾部插入
     */
    public boolean insertLast(int value) {
        if (isFull()) {
            return false;
        } else {
            data[rear] = value;
            rear = (rear + 1) % maxsize;
        }
        return true;
    }

    /**
     * 正常头部删除
     */
    public boolean deleteFront() {
        if (isEmpty()) {
            return false;
        } else {
            front = (front + 1) % maxsize;
        }
        return true;
    }

    /**
     * 尾部删除
     */
    public boolean deleteLast() {
        if (isEmpty()) {
            return false;
        } else {
            rear = (rear + maxsize - 1) % maxsize;
        }
        return true;
    }

    /**
     * Get the front item
     */
    public int getFront() {
        if (isEmpty()) {
            return -1;
        }
        return data[front];
    }

    /**
     * Get the last item from the deque.
     */
    public int getRear() {
        if (isEmpty()) {
            return -1;
        }
        return data[(rear - 1 + maxsize) % maxsize];
    }

    /**
     * Checks whether the circular deque is empty or not.
     */
    public boolean isEmpty() {
        return front == rear;
    }

    /**
     * Checks whether the circular deque is full or not.
     */
    public boolean isFull() {
        return (rear + 1) % maxsize == front;
    }
}

总结

队列相比栈数据结构复杂一些,要记住先进先出的规则,然后用数组或者链表实现即可。

数组实现循环队列,主要是要注意循环取余的妙用,链表实现队列需要考虑为空状态。

而双向队列则是既能当队列又能当栈的一种高效数据结构,掌握还是很有必要的。

系列仓库地址:https://github.com/javasmall/bigsai-algorithm 欢迎star
csdn专栏:数据结构与算法

写一篇原创不易,还请点赞、收藏、关注三连支持一下!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Shopee的折扣活动怎么分类?shopee设置折扣注意事项

旺季到来&#xff0c;Shopee会举办一些折扣活动来吸引客户&#xff0c;那么shopee的折扣活动怎么分类&#xff0c;shopee设置折扣注意事项&#xff1f; shopee的折扣活动怎么分类&#xff1f; 满减活动&#xff1a;满减活动是虾皮常见的一种折扣形式。在这种活动中&#xff0…

Citespace的使用

CiteSpace CiteSpace的相关介绍运行CiteSpace CiteSpace的相关介绍 CiteSpace作为一款优秀的文献计量学软件&#xff0c;能够将文献之间的关系以科学知识图谱的方式可视化地展现在我们面前。简单来说&#xff0c;面对海量的文献&#xff0c;CiteSpace能够迅速锁定自己需要关注…

数据结构与算法C语言版学习笔记(6)-树、二叉树、赫夫曼树

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、树的定义1.结点的度、树的度2.结点的逻辑关系3.树的深度4.有序树和无序树5.森林 二、树的存储结构&#xff08;1&#xff09;双亲表示法&#xff08;2&…

Android 使用.9图 NinePatchDrawable实现动态聊天气泡

最近一段时间&#xff0c;在做一个需求&#xff0c;需要实现一个聊天气泡的动画效果&#xff0c;如下图所示&#xff1a; GitHub源码demo &#xff0c;建议下载demo&#xff0c;运行查看。 动态聊天气泡动画 静态聊天气泡 经过一段时间调研&#xff0c;实现方案如下: 实现方…

FM3793A-高性能PWM控制芯片 超低成本18W-20W 恒功率PD快充

产品描述&#xff1a; FM3793A是一款应用于离线反激式转换器中的高性能电流模式PWM控制器。在 FM3793A中&#xff0c;PWM开关频率最大为65KHz。在轻载和空载条件下&#xff0c;该FM3793A启动间歇模式从而降低开关频率。FM3793A具有丰富的芯片异常状况保护功能&#xff0c;如欠压…

力扣:160. 相交链表(Python3)

题目&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;…

基于insightface实现的人脸检测,人脸识别,insightface源码讲解。

目录 1.搭建insightface需要的环境 2.下载insightface工程 3.代码工程文件讲解 3.1 python-package 3.2 进行测试 3.3 examples 4. 人脸识别 5.代码理解&#xff1a; 1.搭建insightface需要的环境 埋个坑&#xff0c;后续再写&#xff0c;笔者在安装过程中遇到了一些问题。…

人工智能基础——Python:运行效率与时间复杂度

人工智能的学习之路非常漫长&#xff0c;不少人因为学习路线不对或者学习内容不够专业而举步难行。不过别担心&#xff0c;我为大家整理了一份600多G的学习资源&#xff0c;基本上涵盖了人工智能学习的所有内容。点击下方链接,0元进群领取学习资源,让你的学习之路更加顺畅!记得…

浅析CC中的点云配准为什么效果好于PCL?

公众号致力于分享点云处理&#xff0c;SLAM&#xff0c;三维视觉&#xff0c;高精地图相关的文章与技术&#xff0c;欢迎各位加入我们&#xff0c;一起交流一起进步,有兴趣的可联系微信&#xff1a;cloudpoint9527。本文来自点云PCL博主的分享&#xff0c;未经作者允许请勿转载…

最新大麦订单生成器 大麦订单图一键生成

1、8.6全新版 本次更新了四种订单模板生成 多模板自由切换 2、在软件中输入生成的信息&#xff0c;这里输入的是商品信息&#xff0c;选择生成的商品图片&#xff0c;最后生成即可 新版大麦订单生成 四种模板图样式展示 这个样式图就是在大麦生成完的一个订单截图&#xff…

大数据毕业设计选题推荐-生产大数据平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

内网安全-基础设施构建-cobaltstrike远控工具beacon使用

kali在CS文件目录下&#xff0c;打开终端,运行命令&#xff1a; /teamserver 192.168.77.128 123456 在windows中双击bat文件&#xff1a; 填写图下信息&#xff1a; 双击运行&#xff0c;CS上线 自查方法&#xff1a;1、kali与物理机可互通 2、物理机与windows10跳板…

[SHCTF]web方向wp

[SHCTF]web方向wp [WEEK1]babyRCE题目源码wp [WEEK1]1zzphp题目源码wp [WEEK1]ez_serialize题目源码wp [WEEK1]登录就给flag题目wp [WEEK1]生成你的邀请函吧~题目源码wp [WEEK1]飞机大战题目wp [WEEK1]ezphp题目源码wp [WEEK2]no_wake_up题目源码wp [WEEK2]MD5的事就拜托了题目…

人工智能在汽车业应用的五项挑战

在汽车行业扩展人工智能应用时需要注意的问题 随着更多企业投资于汽车人工智能 (AI) 解决方案&#xff0c;我们也愈加接近大规模部署 5 级全自动驾驶汽车。汽车行业的组织如果希望加入这场 AI 带来的颠覆性变革&#xff0c;就应该已提前考虑如何成功和大规模地将人工智能部署到…

C语言每日一题(28) 反转链表

牛客网 BM1 反转链表 题目描述 描述 给定一个单链表的头结点pHead(该头节点是有值的&#xff0c;比如在下图&#xff0c;它的val是1)&#xff0c;长度为n&#xff0c;反转该链表后&#xff0c;返回新链表的表头。 数据范围&#xff1a; 0≤n≤1000 要求&#xff1a;空间复…

LCD1602设计(1)

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

stm32超声波测距不准的解决方法(STM32 delay_us()产生1us)

首先要说明一下原理&#xff1a;使用stm32无法准确产生1us的时间&#xff0c;但是超声波测距一定要依赖时间&#xff0c;时间不准&#xff0c;距离一定不准&#xff0c;这是要肯定的&#xff0c;但是在不准确的情况下&#xff0c;要测量一个比较准确的时间&#xff0c;那么只能…

JavaScript从入门到精通系列第三十三篇:详解正则表达式语法(二)

文章目录 一&#xff1a;正则表达式 1&#xff1a; 检查一个字符串中是否有. 2&#xff1a;第二种关键表达 3&#xff1a;第三种关键表达 ​编辑4&#xff1a;第四种关键表达 5&#xff1a;第五种关键表达 6&#xff1a;第六种关键表达 二&#xff1a;核心表达二 1&am…

在程序中链接静态库

现在我们把上面src目录中的add.cpp、div.cpp、mult.cpp、sub.cpp编译成一个静态库文件libcalc.a。 add_library(库名称 STATIC 源文件1 [源文件2] ...) link_libraries(<static lib> [<static lib>...]) 参数1&#xff1a;指定出要链接的静态库的名字 可以是全…

Postgres的级数生成函数generate_series应用

Postgres的级数生成函数generate_series应用 引用&#xff1a;http://postgres.cn/docs/12/functions-srf.html 函数文档 函数 参数类型 返回类型 描述 generate_series(start, stop) int、bigint或者numeric setof int、setof bigint或者setof numeric&#xff08;与参数类型相…